/*-------------------------------------------------------------------------
 *
 * xml.c
 *	  XML数据类型支持。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/backend/utils/adt/xml.c
 *
 *-------------------------------------------------------------------------
 */

/*
 * 通常，仅在构建期间配置了 libxml 使用时，才提供 XML 类型支持。
 * 但是，即使没有配置，类型和所有函数仍然可用，但大多数都会
 * 失败。一方面，这避免了管理变体目录安装的麻烦。但这也产生了
 * 很好的效果，例如，即使服务器未与 libxml 链接，也可以转储
 * 包含 XML 类型数据的数据库。因此，请确保 xml_out() 即使在
 * 其他函数无法工作时也能正常工作。
 */

/*
 * 有关内存管理的说明：
 *
 * 有时 libxml 会分配全局结构，以期将来可以重用它们。
 * 这使得在运行时更改 xmlMemSetup 功能变得不切实际；
 * 这很可能导致尝试 pfree() 使用 malloc() 分配的块
 * 或反之亦然。由于 libxml 可能被可加载模块使用，例如
 * libperl，我们唯一安全的选择是选择在 postmaster/backend 启动时
 * 更改函数，或者根本不更改。因为我们不希望在可能永远不使用
 * libxml 的会话中激活它，因此后者是首选选择。
 * 但是，出于调试目的，将 libxml 的分配限制在特定的 palloc 上下文中
 * 是非常方便的，这样它们就容易跟踪。因此，这里有代码可以在调试构建中启用
 * 以将 libxml 的分配重定向到一个特殊的上下文 LibxmlContext。
 * 不建议在生产构建中启用此功能，因为可能与外部模块发生不良交互。
 */
/* #define USE_LIBXMLCONTEXT */

#include "postgres.h"

#ifdef USE_LIBXML
#include <libxml/chvalid.h>
#include <libxml/parser.h>
#include <libxml/parserInternals.h>
#include <libxml/tree.h>
#include <libxml/uri.h>
#include <libxml/xmlerror.h>
#include <libxml/xmlversion.h>
#include <libxml/xmlwriter.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>

/*
 * 我们过去通过配置测试检查 xmlStructuredErrorContext；
 * 但在 Windows 上不起作用，因此改用这种较差的方法来测试
 * 库版本号。
 */
#if LIBXML_VERSION >= 20704
#define HAVE_XMLSTRUCTUREDERRORCONTEXT 1
#endif

/*
 * libxml2 2.12 决定在错误处理 API 中插入 "const"。
 */
#if LIBXML_VERSION >= 21200
#define PgXmlErrorPtr const xmlError *
#else
#define PgXmlErrorPtr xmlErrorPtr
#endif

#endif							/* USE_LIBXML */

#include "access/htup_details.h"
#include "access/table.h"
#include "catalog/namespace.h"
#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "executor/spi.h"
#include "executor/tablefunc.h"
#include "fmgr.h"
#include "lib/stringinfo.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/execnodes.h"
#include "nodes/nodeFuncs.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/xml.h"


/* GUC 变量 */
int			xmlbinary;
int			xmloption;

#ifdef USE_LIBXML

/* 用于标识 PgXmlErrorContext 的随机数 */
#define ERRCXT_MAGIC	68275028

struct PgXmlErrorContext
{
	int			magic;
	/* 传递给 pg_xml_init 的严格性参数 */
	PgXmlStrictness strictness;
	/* 当前错误状态和累积消息（如果有） */
	bool		err_occurred;
	StringInfoData err_buf;
	/* 先前 libxml 错误处理状态（由 pg_xml_init 保存） */
	xmlStructuredErrorFunc saved_errfunc;
	void	   *saved_errcxt;
	/* 先前的 libxml 实体处理程序（由 pg_xml_init 保存） */
	xmlExternalEntityLoader saved_entityfunc;
};

static xmlParserInputPtr fc_xmlPgEntityLoader(const char *fc_URL, const char *fc_ID,
										   xmlParserCtxtPtr fc_ctxt);
static void fc_xml_errorHandler(void *fc_data, PgXmlErrorPtr fc_error);
static void fc_xml_ereport_by_code(int fc_level, int fc_sqlcode,
								const char *fc_msg, int fc_errcode);
static void fc_chopStringInfoNewlines(StringInfo fc_str);
static void fc_appendStringInfoLineSeparator(StringInfo fc_str);

#ifdef USE_LIBXMLCONTEXT

static MemoryContext LibxmlContext = NULL;

static void fc_xml_memory_init(void);
static void *fc_xml_palloc(size_t fc_size);
static void *fc_xml_repalloc(void *fc_ptr, size_t fc_size);
static void fc_xml_pfree(void *fc_ptr);
static char *fc_xml_pstrdup(const char *fc_string);
#endif							/* USE_LIBXMLCONTEXT */

static xmlChar *fc_xml_text2xmlChar(text *fc_in);
static int	fc_parse_xml_decl(const xmlChar *fc_str, size_t *fc_lenp,
						   xmlChar **fc_version, xmlChar **fc_encoding, int *fc_standalone);
static bool fc_print_xml_decl(StringInfo fc_buf, const xmlChar *fc_version,
						   pg_enc fc_encoding, int fc_standalone);
static bool fc_xml_doctype_in_content(const xmlChar *fc_str);
static xmlDocPtr fc_xml_parse(text *fc_data, XmlOptionType fc_xmloption_arg,
						   bool fc_preserve_whitespace, int fc_encoding);
static text *fc_xml_xmlnodetoxmltype(xmlNodePtr fc_cur, PgXmlErrorContext *fc_xmlerrcxt);
static int	fc_xml_xpathobjtoxmlarray(xmlXPathObjectPtr fc_xpathobj,
								   ArrayBuildState *fc_astate,
								   PgXmlErrorContext *fc_xmlerrcxt);
static xmlChar *pg_xmlCharStrndup(const char *fc_str, size_t fc_len);
#endif							/* USE_LIBXML */

static void fc_xmldata_root_element_start(StringInfo fc_result, const char *fc_eltname,
									   const char *fc_xmlschema, const char *fc_targetns,
									   bool fc_top_level);
static void fc_xmldata_root_element_end(StringInfo fc_result, const char *fc_eltname);
static StringInfo fc_query_to_xml_internal(const char *fc_query, char *fc_tablename,
										const char *fc_xmlschema, bool fc_nulls, bool fc_tableforest,
										const char *fc_targetns, bool fc_top_level);
static const char *fc_map_sql_table_to_xmlschema(TupleDesc fc_tupdesc, Oid fc_relid,
											  bool fc_nulls, bool fc_tableforest, const char *fc_targetns);
static const char *fc_map_sql_schema_to_xmlschema_types(Oid fc_nspid,
													 List *fc_relid_list, bool fc_nulls,
													 bool fc_tableforest, const char *fc_targetns);
static const char *fc_map_sql_catalog_to_xmlschema_types(List *fc_nspid_list,
													  bool fc_nulls, bool fc_tableforest,
													  const char *fc_targetns);
static const char *fc_map_sql_type_to_xml_name(Oid fc_typeoid, int fc_typmod);
static const char *fc_map_sql_typecoll_to_xmlschema_types(List *fc_tupdesc_list);
static const char *fc_map_sql_type_to_xmlschema_type(Oid fc_typeoid, int fc_typmod);
static void fc_SPI_sql_row_to_xmlelement(uint64 fc_rownum, StringInfo fc_result,
									  char *fc_tablename, bool fc_nulls, bool fc_tableforest,
									  const char *fc_targetns, bool fc_top_level);

/* XMLTABLE 支持 */
#ifdef USE_LIBXML
/* 唯一标识 XmlTableContext 的随机数 */
#define XMLTABLE_CONTEXT_MAGIC	46922182
typedef struct XmlTableBuilderData
{
	int			magic;
	int			natts;
	long int	row_count;
	PgXmlErrorContext *xmlerrcxt;
	xmlParserCtxtPtr ctxt;
	xmlDocPtr	doc;
	xmlXPathContextPtr xpathcxt;
	xmlXPathCompExprPtr xpathcomp;
	xmlXPathObjectPtr xpathobj;
	xmlXPathCompExprPtr *xpathscomp;
} XmlTableBuilderData;
#endif

static void fc_XmlTableInitOpaque(struct TableFuncScanState *fc_state, int fc_natts);
static void fc_XmlTableSetDocument(struct TableFuncScanState *fc_state, Datum fc_value);
static void fc_XmlTableSetNamespace(struct TableFuncScanState *fc_state, const char *fc_name,
								 const char *fc_uri);
static void fc_XmlTableSetRowFilter(struct TableFuncScanState *fc_state, const char *fc_path);
static void fc_XmlTableSetColumnFilter(struct TableFuncScanState *fc_state,
									const char *fc_path, int fc_colnum);
static bool fc_XmlTableFetchRow(struct TableFuncScanState *fc_state);
static Datum fc_XmlTableGetValue(struct TableFuncScanState *fc_state, int fc_colnum,
							  Oid fc_typid, int32 fc_typmod, bool *fc_isnull);
static void fc_XmlTableDestroyOpaque(struct TableFuncScanState *fc_state);

const TableFuncRoutine XmlTableRoutine =
{
	fc_XmlTableInitOpaque,
	fc_XmlTableSetDocument,
	fc_XmlTableSetNamespace,
	fc_XmlTableSetRowFilter,
	fc_XmlTableSetColumnFilter,
	fc_XmlTableFetchRow,
	fc_XmlTableGetValue,
	fc_XmlTableDestroyOpaque
};

#define NO_XML_SUPPORT() \
	ereport(ERROR, \
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
			 errmsg("unsupported XML feature"), \
			 errdetail("This functionality requires the server to be built with libxml support.")))


/* 来自 SQL/XML:2008 第 4.9 节 */
#define NAMESPACE_XSD "http://www.w3.org/2001/XMLSchema"
#define NAMESPACE_XSI "http://www.w3.org/2001/XMLSchema-instance"
#define NAMESPACE_SQLXML "http://standards.iso.org/iso/9075/2003/sqlxml"


#ifdef USE_LIBXML

static int fc_xmlChar_to_encoding(const xmlChar *fc_encoding_name)
{
	int			fc_encoding = pg_char_to_encoding((const char *) fc_encoding_name);

	if (fc_encoding < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid encoding name \"%s\"",
						(const char *) fc_encoding_name)));
	return fc_encoding;
}
#endif


/*
 * xml_in 使用普通的 C 字符串到 VARDATA 的转换，因此目前
 * 我们使用文本数据类型的转换函数。
 *
 * 只要 xmltype 和 text 使用相同的表示，这样是可以接受的。
 */
Datum xml_in(PG_FUNCTION_ARGS)
{
#ifdef USE_LIBXML
	char	   *fc_s = PG_GETARG_CSTRING(0);
	xmltype    *fc_vardata;
	xmlDocPtr	fc_doc;

	fc_vardata = (xmltype *) cstring_to_text(fc_s);

	/*
	 * 解析数据以检查它是否是良构的 XML 数据。假设如果解析失败
	 * 将发生 ERROR。
	 */
	fc_doc = fc_xml_parse(fc_vardata, xmloption, true, GetDatabaseEncoding());
	xmlFreeDoc(fc_doc);

	PG_RETURN_XML_P(fc_vardata);
#else
	NO_XML_SUPPORT();
	return 0;
#endif
}


#define PG_XML_DEFAULT_VERSION "1.0"


/*
 * xml_out_internal 使用普通的 VARDATA 到 C 字符串的转换，因此
 * 目前我们使用文本数据类型的转换函数。
 *
 * 只要 xmltype 和 text 使用相同的表示，这样是可以接受的。
 */
static char * fc_xml_out_internal(xmltype *fc_x, pg_enc fc_target_encoding)
{
	char	   *fc_str = text_to_cstring((text *) fc_x);

#ifdef USE_LIBXML
	size_t		fc_len = strlen(fc_str);
	xmlChar    *fc_version;
	int			fc_standalone;
	int			fc_res_code;

	if ((fc_res_code = fc_parse_xml_decl((xmlChar *) fc_str,
								   &fc_len, &fc_version, NULL, &fc_standalone)) == 0)
	{
		StringInfoData fc_buf;

		initStringInfo(&fc_buf);

		if (!fc_print_xml_decl(&fc_buf, fc_version, fc_target_encoding, fc_standalone))
		{
			/*
			 * 如果我们不打算生成 XML 声明，则在原始字符串中去掉一个
			 * 换行符，以防止输出中出现空的第一行。
			 */
			if (*(fc_str + fc_len) == '\n')
				fc_len += 1;
		}
		appendStringInfoString(&fc_buf, fc_str + fc_len);

		pfree(fc_str);

		return fc_buf.data;
	}

	fc_xml_ereport_by_code(WARNING, ERRCODE_INTERNAL_ERROR,
						"could not parse XML declaration in stored value",
						fc_res_code);
#endif
	return fc_str;
}


Datum xml_out(PG_FUNCTION_ARGS)
{
	xmltype    *fc_x = PG_GETARG_XML_P(0);

	/*
	 * xml_out 在所有情况下移除编码属性。这是因为我们无法从
	 * 这里控制数据项是否会被转换为不同的客户端编码，因此
	 * 包含它会造成更多的坏处而不是好处。
	 */
	PG_RETURN_CSTRING(fc_xml_out_internal(fc_x, 0));
}


Datum xml_recv(PG_FUNCTION_ARGS)
{
#ifdef USE_LIBXML
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);
	xmltype    *fc_result;
	char	   *fc_str;
	char	   *fc_newstr;
	int			fc_nbytes;
	xmlDocPtr	fc_doc;
	xmlChar    *fc_encodingStr = NULL;
	int			fc_encoding;

	/*
	 * 以原始格式读取数据。我们还不知道编码是什么，因为该信息
	 * 嵌入在 XML 声明中；因此我们必须在转换为服务器编码之前
	 * 解析它。
	 */
	fc_nbytes = fc_buf->len - fc_buf->cursor;
	fc_str = (char *) pq_getmsgbytes(fc_buf, fc_nbytes);

	/*
	 * 我们需要一个以 null 结束的字符串以传递给 parse_xml_decl()。
	 * 与其单独复制一份，不如让临时结果比需要的多一字节。
	 */
	fc_result = palloc(fc_nbytes + 1 + VARHDRSZ);
	SET_VARSIZE(fc_result, fc_nbytes + VARHDRSZ);
	memcpy(VARDATA(fc_result), fc_str, fc_nbytes);
	fc_str = VARDATA(fc_result);
	fc_str[fc_nbytes] = '\0';

	fc_parse_xml_decl((const xmlChar *) fc_str, NULL, NULL, &fc_encodingStr, NULL);

	/*
	 * 如果在 XML 头中没有明确指定编码，则将其视为 UTF-8，
	 * 因为这是 XML 的默认编码。这与 xml_in() 不同，
	 * 在那里输入必须经过正常的客户端到服务器编码转换。
	 */
	fc_encoding = fc_encodingStr ? fc_xmlChar_to_encoding(fc_encodingStr) : PG_UTF8;

	/*
	 * 解析数据以检查它是否是良构的 XML 数据。假设如果不
	 * 合格，xml_parse 将抛出 ERROR。
	 */
	fc_doc = fc_xml_parse(fc_result, xmloption, true, fc_encoding);
	xmlFreeDoc(fc_doc);

	/* 现在我们知道该处理什么，转换为服务器编码 */
	fc_newstr = pg_any_to_server(fc_str, fc_nbytes, fc_encoding);

	if (fc_newstr != fc_str)
	{
		pfree(fc_result);
		fc_result = (xmltype *) cstring_to_text(fc_newstr);
		pfree(fc_newstr);
	}

	PG_RETURN_XML_P(fc_result);
#else
	NO_XML_SUPPORT();
	return 0;
#endif
}


Datum xml_send(PG_FUNCTION_ARGS)
{
	xmltype    *fc_x = PG_GETARG_XML_P(0);
	char	   *fc_outval;
	StringInfoData fc_buf;

	/*
	 * xml_out_internal 不转换编码，它只打印正确的声明。
	 * pq_sendtext 将执行转换。
	 */
	fc_outval = fc_xml_out_internal(fc_x, pg_get_client_encoding());

	pq_begintypsend(&fc_buf);
	pq_sendtext(&fc_buf, fc_outval, strlen(fc_outval));
	pfree(fc_outval);
	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}


#ifdef USE_LIBXML
static void fc_appendStringInfoText(StringInfo fc_str, const text *fc_t)
{
	appendBinaryStringInfo(fc_str, VARDATA_ANY(fc_t), VARSIZE_ANY_EXHDR(fc_t));
}
#endif


static xmltype * fc_stringinfo_to_xmltype(StringInfo fc_buf)
{
	return (xmltype *) cstring_to_text_with_len(fc_buf->data, fc_buf->len);
}


static xmltype * fc_cstring_to_xmltype(const char *fc_string)
{
	return (xmltype *) cstring_to_text(fc_string);
}


#ifdef USE_LIBXML
static xmltype * fc_xmlBuffer_to_xmltype(xmlBufferPtr fc_buf)
{
	return (xmltype *) cstring_to_text_with_len((const char *) xmlBufferContent(fc_buf),
												xmlBufferLength(fc_buf));
}
#endif


Datum xmlcomment(PG_FUNCTION_ARGS)
{
#ifdef USE_LIBXML
	text	   *fc_arg = PG_GETARG_TEXT_PP(0);
	char	   *fc_argdata = VARDATA_ANY(fc_arg);
	int			fc_len = VARSIZE_ANY_EXHDR(fc_arg);
	StringInfoData fc_buf;
	int			fc_i;

	/* 检查字符串中的 "--" 或结尾的 "-" */
	for (fc_i = 1; fc_i < fc_len; fc_i++)
	{
		if (fc_argdata[fc_i] == '-' && fc_argdata[fc_i - 1] == '-')
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_XML_COMMENT),
					 errmsg("invalid XML comment")));
	}
	if (fc_len > 0 && fc_argdata[fc_len - 1] == '-')
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_XML_COMMENT),
				 errmsg("invalid XML comment")));

	initStringInfo(&fc_buf);
	appendStringInfoString(&fc_buf, "<!--");
	fc_appendStringInfoText(&fc_buf, fc_arg);
	appendStringInfoString(&fc_buf, "-->");

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(&fc_buf));
#else
	NO_XML_SUPPORT();
	return 0;
#endif
}



/*
 * TODO: xmlconcat 需要合并参数值的符号和未解析实体。
 * 在实践中，这不是很重要。
 */
xmltype * xmlconcat(List *fc_args)
{
#ifdef USE_LIBXML
	int			fc_global_standalone = 1;
	xmlChar    *fc_global_version = NULL;
	bool		fc_global_version_no_value = false;
	StringInfoData fc_buf;
	ListCell   *fc_v;

	initStringInfo(&fc_buf);
	foreach(fc_v, fc_args)
	{
		xmltype    *fc_x = DatumGetXmlP(PointerGetDatum(lfirst(fc_v)));
		size_t		fc_len;
		xmlChar    *fc_version;
		int			fc_standalone;
		char	   *fc_str;

		fc_len = VARSIZE(fc_x) - VARHDRSZ;
		fc_str = text_to_cstring((text *) fc_x);

		fc_parse_xml_decl((xmlChar *) fc_str, &fc_len, &fc_version, NULL, &fc_standalone);

		if (fc_standalone == 0 && fc_global_standalone == 1)
			fc_global_standalone = 0;
		if (fc_standalone < 0)
			fc_global_standalone = -1;

		if (!fc_version)
			fc_global_version_no_value = true;
		else if (!fc_global_version)
			fc_global_version = fc_version;
		else if (xmlStrcmp(fc_version, fc_global_version) != 0)
			fc_global_version_no_value = true;

		appendStringInfoString(&fc_buf, fc_str + fc_len);
		pfree(fc_str);
	}

	if (!fc_global_version_no_value || fc_global_standalone >= 0)
	{
		StringInfoData fc_buf2;

		initStringInfo(&fc_buf2);

		fc_print_xml_decl(&fc_buf2,
					   (!fc_global_version_no_value) ? fc_global_version : NULL,
					   0,
					   fc_global_standalone);

		appendBinaryStringInfo(&fc_buf2, fc_buf.data, fc_buf.len);
		fc_buf = fc_buf2;
	}

	return fc_stringinfo_to_xmltype(&fc_buf);
#else
	NO_XML_SUPPORT();
	return NULL;
#endif
}


/*
 * XMLAGG 支持
 */
Datum xmlconcat2(PG_FUNCTION_ARGS)
{
	if (PG_ARGISNULL(0))
	{
		if (PG_ARGISNULL(1))
			PG_RETURN_NULL();
		else
			PG_RETURN_XML_P(PG_GETARG_XML_P(1));
	}
	else if (PG_ARGISNULL(1))
		PG_RETURN_XML_P(PG_GETARG_XML_P(0));
	else
		PG_RETURN_XML_P(xmlconcat(list_make2(PG_GETARG_XML_P(0),
											 PG_GETARG_XML_P(1))));
}


Datum texttoxml(PG_FUNCTION_ARGS)
{
	text	   *fc_data = PG_GETARG_TEXT_PP(0);

	PG_RETURN_XML_P(xmlparse(fc_data, xmloption, true));
}


Datum xmltotext(PG_FUNCTION_ARGS)
{
	xmltype    *fc_data = PG_GETARG_XML_P(0);

	/* 它实际上是二进制兼容的。 */
	PG_RETURN_TEXT_P((text *) fc_data);
}


text * xmltotext_with_xmloption(xmltype *fc_data, XmlOptionType fc_xmloption_arg)
{
	if (fc_xmloption_arg == XMLOPTION_DOCUMENT && !xml_is_document(fc_data))
		ereport(ERROR,
				(errcode(ERRCODE_NOT_AN_XML_DOCUMENT),
				 errmsg("not an XML document")));

	/* 它实际上是二进制兼容的，除了上面的检查。 */
	return (text *) fc_data;
}


xmltype * xmlelement(XmlExpr *fc_xexpr,
		   Datum *fc_named_argvalue, bool *fc_named_argnull,
		   Datum *fc_argvalue, bool *fc_argnull)
{
#ifdef USE_LIBXML
	xmltype    *fc_result;
	List	   *fc_named_arg_strings;
	List	   *fc_arg_strings;
	int			fc_i;
	ListCell   *fc_arg;
	ListCell   *fc_narg;
	PgXmlErrorContext *fc_xmlerrcxt;
	volatile xmlBufferPtr fc_buf = NULL;
	volatile xmlTextWriterPtr fc_writer = NULL;

	/*
	 * 所有参数已经被评估，其值被传递到 named_argvalue/named_argnull
	 * 或 argvalue/argnull 数组中。这避免了其中一个参数涉及调用
	 * 其他希望根据自己的条件使用 libxml 的函数或子系统时出现问题。
	 * 我们检查原始 XmlExpr 以识别参数的数量和类型。
	 */
	fc_named_arg_strings = NIL;
	fc_i = 0;
	foreach(fc_arg, fc_xexpr->named_args)
	{
		Expr	   *fc_e = (Expr *) lfirst(fc_arg);
		char	   *fc_str;

		if (fc_named_argnull[fc_i])
			fc_str = NULL;
		else
			fc_str = map_sql_value_to_xml_value(fc_named_argvalue[fc_i],
											 exprType((Node *) fc_e),
											 false);
		fc_named_arg_strings = lappend(fc_named_arg_strings, fc_str);
		fc_i++;
	}

	fc_arg_strings = NIL;
	fc_i = 0;
	foreach(fc_arg, fc_xexpr->args)
	{
		Expr	   *fc_e = (Expr *) lfirst(fc_arg);
		char	   *fc_str;

		/* 这里我们可以立即忘记 NULL 元素 */
		if (!fc_argnull[fc_i])
		{
			fc_str = map_sql_value_to_xml_value(fc_argvalue[fc_i],
											 exprType((Node *) fc_e),
											 true);
			fc_arg_strings = lappend(fc_arg_strings, fc_str);
		}
		fc_i++;
	}

	fc_xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);

	PG_TRY();
	{
		fc_buf = xmlBufferCreate();
		if (fc_buf == NULL || fc_xmlerrcxt->err_occurred)
			xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
						"could not allocate xmlBuffer");
		fc_writer = xmlNewTextWriterMemory(fc_buf, 0);
		if (fc_writer == NULL || fc_xmlerrcxt->err_occurred)
			xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
						"could not allocate xmlTextWriter");

		xmlTextWriterStartElement(fc_writer, (xmlChar *) fc_xexpr->name);

		forboth(fc_arg, fc_named_arg_strings, fc_narg, fc_xexpr->arg_names)
		{
			char	   *fc_str = (char *) lfirst(fc_arg);
			char	   *fc_argname = strVal(lfirst(fc_narg));

			if (fc_str)
				xmlTextWriterWriteAttribute(fc_writer,
											(xmlChar *) fc_argname,
											(xmlChar *) fc_str);
		}

		foreach(fc_arg, fc_arg_strings)
		{
			char	   *fc_str = (char *) lfirst(fc_arg);

			xmlTextWriterWriteRaw(fc_writer, (xmlChar *) fc_str);
		}

		xmlTextWriterEndElement(fc_writer);

		/* 我们现在必须执行此操作以将数据刷新到缓冲区... */
		xmlFreeTextWriter(fc_writer);
		fc_writer = NULL;

		fc_result = fc_xmlBuffer_to_xmltype(fc_buf);
	}
	PG_CATCH();
	{
		if (fc_writer)
			xmlFreeTextWriter(fc_writer);
		if (fc_buf)
			xmlBufferFree(fc_buf);

		pg_xml_done(fc_xmlerrcxt, true);

		PG_RE_THROW();
	}
	PG_END_TRY();

	xmlBufferFree(fc_buf);

	pg_xml_done(fc_xmlerrcxt, false);

	return fc_result;
#else
	NO_XML_SUPPORT();
	return NULL;
#endif
}


xmltype * xmlparse(text *fc_data, XmlOptionType fc_xmloption_arg, bool fc_preserve_whitespace)
{
#ifdef USE_LIBXML
	xmlDocPtr	fc_doc;

	fc_doc = fc_xml_parse(fc_data, fc_xmloption_arg, fc_preserve_whitespace,
					GetDatabaseEncoding());
	xmlFreeDoc(fc_doc);

	return (xmltype *) fc_data;
#else
	NO_XML_SUPPORT();
	return NULL;
#endif
}


xmltype * xmlpi(const char *fc_target, text *fc_arg, bool fc_arg_is_null, bool *fc_result_is_null)
{
#ifdef USE_LIBXML
	xmltype    *fc_result;
	StringInfoData fc_buf;

	if (pg_strcasecmp(fc_target, "xml") == 0)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR), /* really */
				 errmsg("invalid XML processing instruction"),
				 errdetail("XML processing instruction target name cannot be \"%s\".", fc_target)));

	/*
	 * 根据 SQL 标准，空值检查在上述语法检查之后进行。
	 */
	*fc_result_is_null = fc_arg_is_null;
	if (*fc_result_is_null)
		return NULL;

	initStringInfo(&fc_buf);

	appendStringInfo(&fc_buf, "<?%s", fc_target);

	if (fc_arg != NULL)
	{
		char	   *fc_string;

		fc_string = text_to_cstring(fc_arg);
		if (strstr(fc_string, "?>") != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION),
					 errmsg("invalid XML processing instruction"),
					 errdetail("XML processing instruction cannot contain \"?>\".")));

		appendStringInfoChar(&fc_buf, ' ');
		appendStringInfoString(&fc_buf, fc_string + strspn(fc_string, " "));
		pfree(fc_string);
	}
	appendStringInfoString(&fc_buf, "?>");

	fc_result = fc_stringinfo_to_xmltype(&fc_buf);
	pfree(fc_buf.data);
	return fc_result;
#else
	NO_XML_SUPPORT();
	return NULL;
#endif
}


xmltype * xmlroot(xmltype *fc_data, text *fc_version, int fc_standalone)
{
#ifdef USE_LIBXML
	char	   *fc_str;
	size_t		fc_len;
	xmlChar    *fc_orig_version;
	int			fc_orig_standalone;
	StringInfoData fc_buf;

	fc_len = VARSIZE(fc_data) - VARHDRSZ;
	fc_str = text_to_cstring((text *) fc_data);

	fc_parse_xml_decl((xmlChar *) fc_str, &fc_len, &fc_orig_version, NULL, &fc_orig_standalone);

	if (fc_version)
		fc_orig_version = fc_xml_text2xmlChar(fc_version);
	else
		fc_orig_version = NULL;

	switch (fc_standalone)
	{
		case XML_STANDALONE_YES:
			fc_orig_standalone = 1;
			break;
		case XML_STANDALONE_NO:
			fc_orig_standalone = 0;
			break;
		case XML_STANDALONE_NO_VALUE:
			fc_orig_standalone = -1;
			break;
		case XML_STANDALONE_OMITTED:
			/* 保留原值 */
			break;
	}

	initStringInfo(&fc_buf);
	fc_print_xml_decl(&fc_buf, fc_orig_version, 0, fc_orig_standalone);
	appendStringInfoString(&fc_buf, fc_str + fc_len);

	return fc_stringinfo_to_xmltype(&fc_buf);
#else
	NO_XML_SUPPORT();
	return NULL;
#endif
}


/*
 * 验证文档（作为字符串给出）是否符合 DTD（作为外部链接给出）
 *
 * 此功能已被移除，因为它存在安全漏洞：没有特权的用户
 * 不应能够使用 Postgres 获取任意外部文件，而不幸的是
 * libxml 恰恰愿意这样做。
 */
Datum xmlvalidate(PG_FUNCTION_ARGS)
{
	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("xmlvalidate is not implemented")));
	return 0;
}


bool xml_is_document(xmltype *fc_arg)
{
#ifdef USE_LIBXML
	bool		fc_result;
	volatile xmlDocPtr fc_doc = NULL;
	MemoryContext fc_ccxt = CurrentMemoryContext;

	/* 我们希望捕获 ereport(INVALID_XML_DOCUMENT) 并返回 false */
	PG_TRY();
	{
		fc_doc = fc_xml_parse((text *) fc_arg, XMLOPTION_DOCUMENT, true,
						GetDatabaseEncoding());
		fc_result = true;
	}
	PG_CATCH();
	{
		ErrorData  *fc_errdata;
		MemoryContext fc_ecxt;

		fc_ecxt = MemoryContextSwitchTo(fc_ccxt);
		fc_errdata = CopyErrorData();
		if (fc_errdata->sqlerrcode == ERRCODE_INVALID_XML_DOCUMENT)
		{
			FlushErrorState();
			fc_result = false;
		}
		else
		{
			MemoryContextSwitchTo(fc_ecxt);
			PG_RE_THROW();
		}
	}
	PG_END_TRY();

	if (fc_doc)
		xmlFreeDoc(fc_doc);

	return fc_result;
#else							/* 非 USE_LIBXML */
	NO_XML_SUPPORT();
	return false;
#endif							/* 非 USE_LIBXML */
}


#ifdef USE_LIBXML

/*
 * pg_xml_init_library --- 设置 libxml 的使用
 *
 * 应该由每个即将使用 libxml 功能的函数调用，但不需要错误处理。
 * 它初始化 libxml 并验证与加载的 libxml 版本的兼容性。
 * 这些是每个会话执行一次的活动。
 *
 * TODO: xmlChar 是 utf8-char，进行适当调整（使用 enc != utf8 的 initdb
 * 和检查）。
 */
void pg_xml_init_library(void)
{
	static bool fc_first_time = true;

	if (fc_first_time)
	{
		/* 我们只需要在每个会话中执行一次的内容 */

		/*
		 * 目前，我们没有对内部使用的纯UTF-8支持 - 检查我们是否能够工作。
		 */
		if (sizeof(char) != sizeof(xmlChar))
			ereport(ERROR,
					(errmsg("could not initialize XML library"),
					 errdetail("libxml2 has incompatible char type: sizeof(char)=%zu, sizeof(xmlChar)=%zu.",
							   sizeof(char), sizeof(xmlChar))));

#ifdef USE_LIBXMLCONTEXT
		/* 按我们的方法设置libxml的内存分配 */
		fc_xml_memory_init();
#endif

		/* 检查库的兼容性 */
		LIBXML_TEST_VERSION;

		fc_first_time = false;
	}
}

/*
 * pg_xml_init --- 设置libxml的使用并注册错误处理程序
 *
 * 每个即将使用libxml功能且需要错误处理的函数都应该调用此函数。
 * 它用pg_xml_init_library()初始化libxml，并建立我们的libxml错误处理程序。
 *
 * 严格性决定了哪些错误被报告，哪些被忽略。
 *
 * 对此函数的调用后面必须跟有一个PG_TRY块，以保证在正常或错误退出期间
 * 调用pg_xml_done()。
 *
 * 这对contrib/xml2导出，以便其他可能希望共享此模块libxml错误处理程序的代码使用。
 */
PgXmlErrorContext *
pg_xml_init(PgXmlStrictness fc_strictness)
{
	PgXmlErrorContext *fc_errcxt;
	void	   *fc_new_errcxt;

	/* 如果需要，则进行一次性设置 */
	pg_xml_init_library();

	/* 创建错误处理上下文结构 */
	fc_errcxt = (PgXmlErrorContext *) palloc(sizeof(PgXmlErrorContext));
	fc_errcxt->magic = ERRCXT_MAGIC;
	fc_errcxt->strictness = fc_strictness;
	fc_errcxt->err_occurred = false;
	initStringInfo(&fc_errcxt->err_buf);

	/*
	 * 保存原始错误处理程序并安装我们的。libxml最初并不区分
	 * 通用和结构化错误处理程序的上下文。如果我们使用旧版本的libxml，
	 * 那么即使我们正在使用结构化错误处理程序，我们也必须保存
	 * 通用错误上下文。
	 */
	fc_errcxt->saved_errfunc = xmlStructuredError;

#ifdef HAVE_XMLSTRUCTUREDERRORCONTEXT
	fc_errcxt->saved_errcxt = xmlStructuredErrorContext;
#else
	fc_errcxt->saved_errcxt = xmlGenericErrorContext;
#endif

	xmlSetStructuredErrorFunc((void *) fc_errcxt, fc_xml_errorHandler);

	/*
	 * 验证xmlSetStructuredErrorFunc是否设置了我们预期的上下文变量。
	 * 如果没有，我们刚刚保存的错误上下文指针就不是正确的恢复对象，
	 * 而且由于这使我们无法在pg_xml_done中恢复上下文，我们必须失败。
	 *
	 * 唯一已知的这种测试失败的情况是，如果我们用不跟踪结构化错误上下文
	 * 的libxml2头编译（< 2.7.4），但在运行时使用跟踪结构化错误上下文的版本，
	 * 或者反之亦然。libxml2的作者并未将此更改视为ABI的中断，
	 * 所以pg_xml_init_library中的LIBXML_TEST_VERSION测试未能保护我们。
	 */

#ifdef HAVE_XMLSTRUCTUREDERRORCONTEXT
	fc_new_errcxt = xmlStructuredErrorContext;
#else
	fc_new_errcxt = xmlGenericErrorContext;
#endif

	if (fc_new_errcxt != (void *) fc_errcxt)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("could not set up XML error handler"),
				 errhint("This probably indicates that the version of libxml2"
						 " being used is not compatible with the libxml2"
						 " header files that PostgreSQL was built with.")));

	/*
	 * 此外，安装一个实体加载程序以防止不必要的外部
	 * 文件和URL的获取。
	 */
	fc_errcxt->saved_entityfunc = xmlGetExternalEntityLoader();
	xmlSetExternalEntityLoader(fc_xmlPgEntityLoader);

	return fc_errcxt;
}


/*
 * pg_xml_done --- 恢复先前的libxml错误处理
 *
 * 将libxml的全局错误处理状态重置为pg_xml_init()被调用之前的状态。
 *
 * 此例程验证所有待处理的错误是否已被处理（无论在assert启用构建中）。
 */
void pg_xml_done(PgXmlErrorContext *fc_errcxt, bool fc_isError)
{
	void	   *fc_cur_errcxt;

	/* 这里的assert似乎是足够的保护 */
	Assert(fc_errcxt->magic == ERRCXT_MAGIC);

	/*
	 * 在正常退出时，不应有未处理的libxml错误。但我们
	 * 不应该在错误恢复期间强制执行这一点，因为longjmp
	 * 在xml_ereport有机会运行之前可能已被抛出。
	 */
	Assert(!fc_errcxt->err_occurred || fc_isError);

	/*
	 * 检查libxml的全局状态是否正确，如果不正确则发出警告。这是一个
	 * 实际测试，而不是Assert，因为发生的概率更高。
	 */
#ifdef HAVE_XMLSTRUCTUREDERRORCONTEXT
	fc_cur_errcxt = xmlStructuredErrorContext;
#else
	fc_cur_errcxt = xmlGenericErrorContext;
#endif

	if (fc_cur_errcxt != (void *) fc_errcxt)
		elog(WARNING, "libxml error handling state is out of sync with xml.c");

	/* 恢复保存的处理程序 */
	xmlSetStructuredErrorFunc(fc_errcxt->saved_errcxt, fc_errcxt->saved_errfunc);
	xmlSetExternalEntityLoader(fc_errcxt->saved_entityfunc);

	/*
	 * 将结构标记为无效，以防万一有人设法以某种方式
	 * 调用xml_errorHandler或xml_ereport。
	 */
	fc_errcxt->magic = 0;

	/* 释放内存 */
	pfree(fc_errcxt->err_buf.data);
	pfree(fc_errcxt);
}


/*
 * pg_xml_error_occurred() --- 测试错误标志
 */
bool pg_xml_error_occurred(PgXmlErrorContext *fc_errcxt)
{
	return fc_errcxt->err_occurred;
}


/*
 * SQL/XML允许存储“XML文档”或“XML内容”。 “XML
 * 文档”由XML规范指定，并且可以通过libxml轻松解析。 “XML内容”由SQL/XML
 * 指定，作为生成“XMLDecl? content”。但libxml只能解析
 * “content”部分，因此我们必须自己解析XML声明
 * 来完成这一点。
 */

#define CHECK_XML_SPACE(p) \
	do { \
		if (!xmlIsBlank_ch(*(p))) \
			return XML_ERR_SPACE_REQUIRED; \
	} while (0)

#define SKIP_XML_SPACE(p) \
	while (xmlIsBlank_ch(*(p))) (p)++

/* 字母 | 数字 | '.' | '-' | '_' | ':' | 组合字符 | 扩展符 */
/* 注意多个评估参数的情况！ */
#define PG_XMLISNAMECHAR(c) \
	(xmlIsBaseChar_ch(c) || xmlIsIdeographicQ(c) \
			|| xmlIsDigit_ch(c) \
			|| c == '.' || c == '-' || c == '_' || c == ':' \
			|| xmlIsCombiningQ(c) \
			|| xmlIsExtender_ch(c))

/* pnstrdup，但处理xmlChar而不是char；长度以xmlChars计算 */
static xmlChar * fc_xml_pnstrdup(const xmlChar *fc_str, size_t fc_len)
{
	xmlChar    *fc_result;

	fc_result = (xmlChar *) palloc((fc_len + 1) * sizeof(xmlChar));
	memcpy(fc_result, fc_str, fc_len * sizeof(xmlChar));
	fc_result[fc_len] = 0;
	return fc_result;
}

/* 同上，输入为char* */
static xmlChar * pg_xmlCharStrndup(const char *fc_str, size_t fc_len)
{
	xmlChar    *fc_result;

	fc_result = (xmlChar *) palloc((fc_len + 1) * sizeof(xmlChar));
	memcpy(fc_result, fc_str, fc_len);
	fc_result[fc_len] = '\0';

	return fc_result;
}

/*
 * 将xmlChar字符串复制到PostgreSQL拥有的内存中，释放输入。
 *
 * 输入的xmlChar在复制成功与否的情况下都会被释放。
 */
static char * fc_xml_pstrdup_and_free(xmlChar *fc_str)
{
	char	   *fc_result;

	if (fc_str)
	{
		PG_TRY();
		{
			fc_result = pstrdup((char *) fc_str);
		}
		PG_FINALLY();
		{
			xmlFree(fc_str);
		}
		PG_END_TRY();
	}
	else
		fc_result = NULL;

	return fc_result;
}

/*
 * str是以null结尾的输入字符串。 其余参数是
 * 输出参数；如果不想要值，则可以为NULL。
 * version和encoding作为局部分配的字符串返回。
 * 如果正常，结果为0，如不正常则为错误代码。
 */
static int fc_parse_xml_decl(const xmlChar *fc_str, size_t *fc_lenp,
			   xmlChar **fc_version, xmlChar **fc_encoding, int *fc_standalone)
{
	const xmlChar *fc_p;
	const xmlChar *fc_save_p;
	size_t		fc_len;
	int			fc_utf8char;
	int			fc_utf8len;

	/*
	 * 仅初始化libxml。我们在这里不需要错误处理，但我们需要
	 * 确保在调用libxml的任何函数之前已初始化libxml。
	 * 请注意，如果调用者已经执行了pg_xml_init()，这将是安全的（且无操作）。
	 */
	pg_xml_init_library();

	/* 将输出参数初始化为“不存在” */
	if (fc_version)
		*fc_version = NULL;
	if (fc_encoding)
		*fc_encoding = NULL;
	if (fc_standalone)
		*fc_standalone = -1;

	fc_p = fc_str;

	if (xmlStrncmp(fc_p, (xmlChar *) "<?xml", 5) != 0)
		goto finished;

	/*
	 * 如果下一个字符是名称字符，则它是类似<?xml-stylesheet ...?>
	 * 的PI，而不是XMLDecl，因此我们已经完成了我们所要做的事情，并且没有
	 * 找到XMLDecl。
	 *
	 * 我们需要xmlGetUTF8Char的输入长度值，但没有必要
	 * 计算整个文档大小，因此使用strnlen而不是strlen。
	 */
	fc_utf8len = strnlen((const char *) (fc_p + 5), MAX_MULTIBYTE_CHAR_LEN);
	fc_utf8char = xmlGetUTF8Char(fc_p + 5, &fc_utf8len);
	if (PG_XMLISNAMECHAR(fc_utf8char))
		goto finished;

	fc_p += 5;

	/* 版本 */
	CHECK_XML_SPACE(fc_p);
	SKIP_XML_SPACE(fc_p);
	if (xmlStrncmp(fc_p, (xmlChar *) "version", 7) != 0)
		return XML_ERR_VERSION_MISSING;
	fc_p += 7;
	SKIP_XML_SPACE(fc_p);
	if (*fc_p != '=')
		return XML_ERR_VERSION_MISSING;
	fc_p += 1;
	SKIP_XML_SPACE(fc_p);

	if (*fc_p == '\'' || *fc_p == '"')
	{
		const xmlChar *fc_q;

		fc_q = xmlStrchr(fc_p + 1, *fc_p);
		if (!fc_q)
			return XML_ERR_VERSION_MISSING;

		if (fc_version)
			*fc_version = fc_xml_pnstrdup(fc_p + 1, fc_q - fc_p - 1);
		fc_p = fc_q + 1;
	}
	else
		return XML_ERR_VERSION_MISSING;

	/* 编码 */
	fc_save_p = fc_p;
	SKIP_XML_SPACE(fc_p);
	if (xmlStrncmp(fc_p, (xmlChar *) "encoding", 8) == 0)
	{
		CHECK_XML_SPACE(fc_save_p);
		fc_p += 8;
		SKIP_XML_SPACE(fc_p);
		if (*fc_p != '=')
			return XML_ERR_MISSING_ENCODING;
		fc_p += 1;
		SKIP_XML_SPACE(fc_p);

		if (*fc_p == '\'' || *fc_p == '"')
		{
			const xmlChar *fc_q;

			fc_q = xmlStrchr(fc_p + 1, *fc_p);
			if (!fc_q)
				return XML_ERR_MISSING_ENCODING;

			if (fc_encoding)
				*fc_encoding = fc_xml_pnstrdup(fc_p + 1, fc_q - fc_p - 1);
			fc_p = fc_q + 1;
		}
		else
			return XML_ERR_MISSING_ENCODING;
	}
	else
	{
		fc_p = fc_save_p;
	}

	/* 独立 */
	fc_save_p = fc_p;
	SKIP_XML_SPACE(fc_p);
	if (xmlStrncmp(fc_p, (xmlChar *) "standalone", 10) == 0)
	{
		CHECK_XML_SPACE(fc_save_p);
		fc_p += 10;
		SKIP_XML_SPACE(fc_p);
		if (*fc_p != '=')
			return XML_ERR_STANDALONE_VALUE;
		fc_p += 1;
		SKIP_XML_SPACE(fc_p);
		if (xmlStrncmp(fc_p, (xmlChar *) "'yes'", 5) == 0 ||
			xmlStrncmp(fc_p, (xmlChar *) "\"yes\"", 5) == 0)
		{
			if (fc_standalone)
				*fc_standalone = 1;
			fc_p += 5;
		}
		else if (xmlStrncmp(fc_p, (xmlChar *) "'no'", 4) == 0 ||
				 xmlStrncmp(fc_p, (xmlChar *) "\"no\"", 4) == 0)
		{
			if (fc_standalone)
				*fc_standalone = 0;
			fc_p += 4;
		}
		else
			return XML_ERR_STANDALONE_VALUE;
	}
	else
	{
		fc_p = fc_save_p;
	}

	SKIP_XML_SPACE(fc_p);
	if (xmlStrncmp(fc_p, (xmlChar *) "?>", 2) != 0)
		return XML_ERR_XMLDECL_NOT_FINISHED;
	fc_p += 2;

finished:
	fc_len = fc_p - fc_str;

	for (fc_p = fc_str; fc_p < fc_str + fc_len; fc_p++)
		if (*fc_p > 127)
			return XML_ERR_INVALID_CHAR;

	if (fc_lenp)
		*fc_lenp = fc_len;

	return XML_ERR_OK;
}



/*
 * 编写一个XML声明。在输出时，我们调整XML声明如下。(这些规则是SQL标准中“XML值的序列化”条款的道德等价物。)
 *
 * 如果可能的话，我们尽量避免生成XML声明。这是为了防止像xml '<foo/>'的简单情况结果为'<?xml version="1.0"?><foo/>’，这肯定是令人烦恼的。如果指定了独立属性或者包含编码声明，我们必须提供声明。如果有声明，我们必须指定一个版本（XML要求这样）。否则，只有在版本不是“1.0”时，我们才会生成声明，这也是SQL:2003中指定的默认版本。
 */
static bool fc_print_xml_decl(StringInfo fc_buf, const xmlChar *fc_version,
			   pg_enc fc_encoding, int fc_standalone)
{
	if ((fc_version && strcmp((const char *) fc_version, PG_XML_DEFAULT_VERSION) != 0)
		|| (fc_encoding && fc_encoding != PG_UTF8)
		|| fc_standalone != -1)
	{
		appendStringInfoString(fc_buf, "<?xml");

		if (fc_version)
			appendStringInfo(fc_buf, " version=\"%s\"", fc_version);
		else
			appendStringInfo(fc_buf, " version=\"%s\"", PG_XML_DEFAULT_VERSION);

		if (fc_encoding && fc_encoding != PG_UTF8)
		{
			/*
			 * XXX可能有必要将其转换为IANA名称（ISO-8859-1而不是LATIN1等）；需要现场经验
			 */
			appendStringInfo(fc_buf, " encoding=\"%s\"",
							 pg_encoding_to_char(fc_encoding));
		}

		if (fc_standalone == 1)
			appendStringInfoString(fc_buf, " standalone=\"yes\"");
		else if (fc_standalone == 0)
			appendStringInfoString(fc_buf, " standalone=\"no\"");
		appendStringInfoString(fc_buf, "?>");

		return true;
	}
	else
		return false;
}

/*
 * 测试作为CONTENT被解析的输入是否包含DTD。
 *
 * SQL/XML:2003对CONTENT的定义（"XMLDecl? content"）并不满足于包含DTD的文档，这有点尴尬，因为这意味着CONTENT类型不是DOCUMENT的一个适当超集。SQL/XML:2006及后续版本通过重新定义内容以参考XQuery/XPath数据模型的“更宽松”文档节点来修复这一点，使得任何DOCUMENT值确实也是一个CONTENT值。这个定义更有用，因为CONTENT变得可以用于解析未知形式的输入（想想pg_restore）。
 *
 * 在下面的parse_xml中，当解析CONTENT时，libxml并没有给我们2006+的行为，只给了2003；如果输入包含DTD，它将会崩溃。但是我们可以很容易地提供2006+的CONTENT定义，通过首先检测这种情况并简单地将解析作为DOCUMENT进行。
 *
 * DTD可以在任意远的地方找到，但这将是一个牵强的情况；它通常会在几十个字符内开始。唯一可以在其之前的东西是XMLDecl（在这里，调用者已经调用了parse_xml_decl），空白字符、注释和处理指令。这个函数只需在看到有效序列导致<!DOCTYPE时返回true。在任何其他情况下，包括格式错误的输入，它可以简单地返回false；这意味着输入将按照最初的计划作为CONTENT进行解析，libxml会报告任何错误。
 *
 * 这只能从xml_parse调用，当pg_xml_init已经被调用时。输入已经是UTF8编码。
 */
static bool fc_xml_doctype_in_content(const xmlChar *fc_str)
{
	const xmlChar *fc_p = fc_str;

	for (;;)
	{
		const xmlChar *fc_e;

		SKIP_XML_SPACE(fc_p);
		if (*fc_p != '<')
			return false;
		fc_p++;

		if (*fc_p == '!')
		{
			fc_p++;

			/* 如果我们看到 <!DOCTYPE，就可以返回 true */
			if (xmlStrncmp(fc_p, (xmlChar *) "DOCTYPE", 7) == 0)
				return true;

			/* 否则，如果它不是一个注释，失败 */
			if (xmlStrncmp(fc_p, (xmlChar *) "--", 2) != 0)
				return false;
			/* 查找注释的结束：查找 -- 并且 > 必须跟随 */
			fc_p = xmlStrstr(fc_p + 2, (xmlChar *) "--");
			if (!fc_p || fc_p[2] != '>')
				return false;
			/* 前进到注释，并继续扫描 */
			fc_p += 3;
			continue;
		}

		/* 否则，如果它不是一个 PI <?target something?>，失败 */
		if (*fc_p != '?')
			return false;
		fc_p++;

		/* 查找 PI 的结束（在 PI 内部是禁止使用字符串 ?> 的） */
		fc_e = xmlStrstr(fc_p, (xmlChar *) "?>");
		if (!fc_e)
			return false;

		/* 前进到 PI，继续扫描 */
		fc_p = fc_e + 2;
	}
}


/*
 * 将 C 字符串转换为 XML 内部表示
 *
 * 注意：释放结果的责任在于调用者，必须调用 xmlFreeDoc()，
 * 否则将会导致持久的内存泄漏！
 *
 * TODO 也许 libxml2 的 xmlreader 更好？（不构造 DOM，
 * 但也不使用 SAX - 见 xmlreader.c）
 */
static xmlDocPtr
fc_xml_parse(text *fc_data, XmlOptionType fc_xmloption_arg, bool fc_preserve_whitespace,
		  int fc_encoding)
{
	int32		fc_len;
	xmlChar    *fc_string;
	xmlChar    *fc_utf8string;
	PgXmlErrorContext *fc_xmlerrcxt;
	volatile xmlParserCtxtPtr fc_ctxt = NULL;
	volatile xmlDocPtr fc_doc = NULL;

	fc_len = VARSIZE_ANY_EXHDR(fc_data);	/* 后面会用到 */
	fc_string = fc_xml_text2xmlChar(fc_data);

	fc_utf8string = pg_do_encoding_conversion(fc_string,
										   fc_len,
										   fc_encoding,
										   PG_UTF8);

	/* 启动 libxml 及其解析器 */
	fc_xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_WELLFORMED);

	/* 使用 TRY 块以确保我们正确清理 */
	PG_TRY();
	{
		bool		fc_parse_as_document = false;
		int			fc_options;
		int			fc_res_code;
		size_t		fc_count = 0;
		xmlChar    *fc_version = NULL;
		int			fc_standalone = 0;

		xmlInitParser();

		/* 决定是作为文档还是内容进行解析 */
		if (fc_xmloption_arg == XMLOPTION_DOCUMENT)
			fc_parse_as_document = true;
		else
		{
			/* 解析并跳过 XML 声明（如果有的话） */
			fc_res_code = fc_parse_xml_decl(fc_utf8string,
									  &fc_count, &fc_version, NULL, &fc_standalone);
			if (fc_res_code != 0)
				fc_xml_ereport_by_code(ERROR, ERRCODE_INVALID_XML_CONTENT,
									"invalid XML content: invalid XML declaration",
									fc_res_code);

			/* 是否存在 DOCTYPE 元素？ */
			if (fc_xml_doctype_in_content(fc_utf8string + fc_count))
				fc_parse_as_document = true;
		}

		/*
		 * 选择解析选项。
		 *
		 * 注意，这里我们尝试根据 SQL/XML:2008 GR 10.16.7.d 应用 DTD 默认值 ('内部 DTD 定义的默认值被应用') (XML_PARSE_DTDATTR)。
		 * 至于外部 DTD，我们也尝试支持它们（见 SQL/XML:2008 GR 10.16.7.e），
		 * 但这实际上并没有发生，因为 xmlPgEntityLoader 阻止了这一点。
		 */
		fc_options = XML_PARSE_NOENT | XML_PARSE_DTDATTR
			| (fc_preserve_whitespace ? 0 : XML_PARSE_NOBLANKS);

		if (fc_parse_as_document)
		{
			fc_ctxt = xmlNewParserCtxt();
			if (fc_ctxt == NULL || fc_xmlerrcxt->err_occurred)
				xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
							"could not allocate parser context");

			fc_doc = xmlCtxtReadDoc(fc_ctxt, fc_utf8string,
								 NULL,	/* no URL */
								 "UTF-8",
								 fc_options);

			if (fc_doc == NULL || fc_xmlerrcxt->err_occurred)
			{
				/* 使用原始选项决定抛出哪个错误代码 */
				if (fc_xmloption_arg == XMLOPTION_DOCUMENT)
					xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
								"invalid XML document");
				else
					xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_INVALID_XML_CONTENT,
								"invalid XML content");
			}
		}
		else
		{
			xmlNodePtr	fc_root;

			/* 设置包含空根节点的文档作为上下文节点 */
			fc_doc = xmlNewDoc(fc_version);
			Assert(fc_doc->encoding == NULL);
			fc_doc->encoding = xmlStrdup((const xmlChar *) "UTF-8");
			fc_doc->standalone = fc_standalone;

			fc_root = xmlNewNode(NULL, (const xmlChar *) "content-root");
			if (fc_root == NULL || fc_xmlerrcxt->err_occurred)
				xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
							"could not allocate xml node");
			/* 这将根节点附加到文档，因此我们无需单独释放它。 */
			xmlDocSetRootElement(fc_doc, fc_root);

			/* 允许空内容 */
			if (*(fc_utf8string + fc_count))
			{
				xmlNodePtr	fc_node_list = NULL;
				xmlParserErrors fc_res;

				fc_res = xmlParseInNodeContext(fc_root,
											(char *) fc_utf8string + fc_count,
											strlen((char *) fc_utf8string + fc_count),
											fc_options,
											&fc_node_list);

				xmlFreeNodeList(fc_node_list);

				if (fc_res != XML_ERR_OK || fc_xmlerrcxt->err_occurred)
					xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_INVALID_XML_CONTENT,
								"invalid XML content");
			}
		}
	}
	PG_CATCH();
	{
		if (fc_doc != NULL)
			xmlFreeDoc(fc_doc);
		if (fc_ctxt != NULL)
			xmlFreeParserCtxt(fc_ctxt);

		pg_xml_done(fc_xmlerrcxt, true);

		PG_RE_THROW();
	}
	PG_END_TRY();

	if (fc_ctxt != NULL)
		xmlFreeParserCtxt(fc_ctxt);

	pg_xml_done(fc_xmlerrcxt, false);

	return fc_doc;
}


/*
 * xmlChar<->text 转换
 */
static xmlChar * fc_xml_text2xmlChar(text *fc_in)
{
	return (xmlChar *) text_to_cstring(fc_in);
}


#ifdef USE_LIBXMLCONTEXT

/*
 * 管理用于所有 libxml 分配的特殊上下文（但仅在特殊调试构建中；见文件顶部的说明）
 */
static void fc_xml_memory_init(void)
{
	/* 如果尚未创建，则创建内存上下文 */
	if (LibxmlContext == NULL)
		LibxmlContext = AllocSetContextCreate(TopMemoryContext,
											  "Libxml context",
											  ALLOCSET_DEFAULT_SIZES);

	/* 即使已设置也要重新建立回调 */
	xmlMemSetup(fc_xml_pfree, fc_xml_palloc, fc_xml_repalloc, fc_xml_pstrdup);
}

/*
 * 内存管理函数的包装器
 */
static void * fc_xml_palloc(size_t fc_size)
{
	return MemoryContextAlloc(LibxmlContext, fc_size);
}


static void * fc_xml_repalloc(void *fc_ptr, size_t fc_size)
{
	return repalloc(fc_ptr, fc_size);
}


static void fc_xml_pfree(void *fc_ptr)
{
	/* libxml 的某些部分假设 xmlFree(NULL) 是允许的 */
	if (fc_ptr)
		pfree(fc_ptr);
}


static char * fc_xml_pstrdup(const char *fc_string)
{
	return MemoryContextStrdup(LibxmlContext, fc_string);
}
#endif							/* USE_LIBXMLCONTEXT */


/*
 * xmlPgEntityLoader --- 实体加载器回调函数
 *
 * 安静地防止任何外部实体 URL 被加载。我们不想抛出错误，
 * 因此让实体看起来扩展为空字符串。
 *
 * 我们更希望允许加载存在于系统全局 XML 目录中的实体；
 * 但可用的 libxml2 API 使这变得复杂而脆弱。现在，只需关闭所有外部访问。
 */
static xmlParserInputPtr
fc_xmlPgEntityLoader(const char *fc_URL, const char *fc_ID,
				  xmlParserCtxtPtr fc_ctxt)
{
	return xmlNewStringInputStream(fc_ctxt, (const xmlChar *) "");
}


/*
 * xml_ereport --- 报告与 XML 相关的错误
 *
 * “msg”是 SQL 级别的消息；一些可以从 SQL/XML
 * 标准中采纳。此函数会添加 libxml 的本地错误消息（如果有）作为详细信息。
 *
 * 这对希望共享核心 libxml 错误处理程序的模块是公开的。
 * 注意，必须先调用 pg_xml_init()。
 */
void xml_ereport(PgXmlErrorContext *fc_errcxt, int fc_level, int fc_sqlcode, const char *fc_msg)
{
	char	   *fc_detail;

	/* 防止有人传递给我们一个无效的上下文结构 */
	if (fc_errcxt->magic != ERRCXT_MAGIC)
		elog(ERROR, "xml_ereport called with invalid PgXmlErrorContext");

	/* 标记当前的 libxml 错误已被报告 */
	fc_errcxt->err_occurred = false;

	/* 仅当我们有来自 libxml 的一些文本时才包含详细信息 */
	if (fc_errcxt->err_buf.len > 0)
		fc_detail = fc_errcxt->err_buf.data;
	else
		fc_detail = NULL;

	ereport(fc_level,
			(errcode(fc_sqlcode),
			 errmsg_internal("%s", fc_msg),
			 fc_detail ? errdetail_internal("%s", fc_detail) : 0));
}


/*
 * libxml 错误和警告的错误处理程序
 */
static void fc_xml_errorHandler(void *fc_data, PgXmlErrorPtr fc_error)
{
	PgXmlErrorContext *fc_xmlerrcxt = (PgXmlErrorContext *) fc_data;
	xmlParserCtxtPtr fc_ctxt = (xmlParserCtxtPtr) fc_error->ctxt;
	xmlParserInputPtr fc_input = (fc_ctxt != NULL) ? fc_ctxt->input : NULL;
	xmlNodePtr	fc_node = fc_error->node;
	const xmlChar *fc_name = (fc_node != NULL &&
						   fc_node->type == XML_ELEMENT_NODE) ? fc_node->name : NULL;
	int			fc_domain = fc_error->domain;
	int			fc_level = fc_error->level;
	StringInfo	fc_errorBuf;

	/*
	 * 防止有人传递一个虚假的上下文结构。
	 *
	 * 如果此检查失败，我们强制后端退出，因为从 libxml 的长跳转
	 * 可能会使其在进一步使用中不安全。
	 */
	if (fc_xmlerrcxt->magic != ERRCXT_MAGIC)
		elog(FATAL, "xml_errorHandler called with invalid PgXmlErrorContext");

	/*----------
	 * 较旧的 libxml 版本以不同的方式报告某些错误。
	 * 首先，一些错误之前被报告为来自解析器
	 * 领域，但现在报告为来自命名空间领域。
	 * 其次，一些警告升级为错误。
	 * 我们试图在这里进行补偿。
	 *----------
	 */
	switch (fc_error->code)
	{
		case XML_WAR_NS_URI:
			fc_level = XML_ERR_ERROR;
			fc_domain = XML_FROM_NAMESPACE;
			break;

		case XML_ERR_NS_DECL_ERROR:
		case XML_WAR_NS_URI_RELATIVE:
		case XML_WAR_NS_COLUMN:
		case XML_NS_ERR_XML_NAMESPACE:
		case XML_NS_ERR_UNDEFINED_NAMESPACE:
		case XML_NS_ERR_QNAME:
		case XML_NS_ERR_ATTRIBUTE_REDEFINED:
		case XML_NS_ERR_EMPTY:
			fc_domain = XML_FROM_NAMESPACE;
			break;
	}

	/* 决定是否对错误采取行动 */
	switch (fc_domain)
	{
		case XML_FROM_PARSER:

			/*
			 * XML_ERR_NOT_WELL_BALANCED 通常是在一些
			 * 其他更相关的错误后报告的。此外，libxml2 2.13 在
			 * 一套完全不同的规则下报告它，而不是先前的
			 * 版本。为了避免跨版本的行为差异，
			 * 只要我们已经日志记录了一些错误，就抑制它。
			 */
			if (fc_error->code == XML_ERR_NOT_WELL_BALANCED &&
				fc_xmlerrcxt->err_occurred)
				return;
			/* fall through */

		case XML_FROM_NONE:
		case XML_FROM_MEMORY:
		case XML_FROM_IO:

			/*
			 * 抑制关于未声明实体的警告。我们需要这样做
			 * 以避免由于未加载 DTD 定义而出现的问题。
			 */
			if (fc_error->code == XML_WAR_UNDECLARED_ENTITY)
				return;

			/* 否则，无论解析目的如何，接受错误 */
			break;

		default:
			/* 如果只进行格式正确性检查，则忽略错误 */
			if (fc_xmlerrcxt->strictness == PG_XML_STRICTNESS_WELLFORMED)
				return;
			break;
	}

	/* 在 errorBuf 中准备错误消息 */
	fc_errorBuf = makeStringInfo();

	if (fc_error->line > 0)
		appendStringInfo(fc_errorBuf, "line %d: ", fc_error->line);
	if (fc_name != NULL)
		appendStringInfo(fc_errorBuf, "element %s: ", fc_name);
	if (fc_error->message != NULL)
		appendStringInfoString(fc_errorBuf, fc_error->message);
	else
		appendStringInfoString(fc_errorBuf, "(no message provided)");

	/*
	 * 将上下文信息附加到 errorBuf。
	 *
	 * xmlParserPrintFileContext() 使用 libxml 的“通用”错误处理程序来
	 * 写入上下文。由于我们不想在这里重复 libxml
	 * 的功能，因此我们暂时设置一个通用错误处理程序。
	 *
	 * 我们直接使用 appendStringInfo() 作为 libxml 的通用错误处理程序。
	 * 这应该可以工作，因为它本质上具有与 libxml 所期望的相同的签名，
	 * 即 (void *ptr, const char *msg, ...)。
	 */
	if (fc_input != NULL)
	{
		xmlGenericErrorFunc fc_errFuncSaved = xmlGenericError;
		void	   *fc_errCtxSaved = xmlGenericErrorContext;

		xmlSetGenericErrorFunc((void *) fc_errorBuf,
							   (xmlGenericErrorFunc) appendStringInfo);

		/* 将上下文信息添加到 errorBuf */
		fc_appendStringInfoLineSeparator(fc_errorBuf);

		xmlParserPrintFileContext(fc_input);

		/* 恢复通用错误函数 */
		xmlSetGenericErrorFunc(fc_errCtxSaved, fc_errFuncSaved);
	}

	/* 去掉 errorBuf 中的任何尾随换行符 */
	fc_chopStringInfoNewlines(fc_errorBuf);

	/*
	 * 传统的错误处理模式。err_occurred 永远不会被设置，我们只是将
	 * 消息添加到 err_buf。此模式存在的原因是 xml2 贡献模块
	 * 使用我们错误处理的基础设施，但由于它已被弃用，所以我们不想改变其
	 * 行为。这也是为什么我们在这里不区分通知、警告和错误的原因——旧式
	 * 的通用错误处理程序也不会这样做。
	 */
	if (fc_xmlerrcxt->strictness == PG_XML_STRICTNESS_LEGACY)
	{
		fc_appendStringInfoLineSeparator(&fc_xmlerrcxt->err_buf);
		appendBinaryStringInfo(&fc_xmlerrcxt->err_buf, fc_errorBuf->data,
							   fc_errorBuf->len);

		pfree(fc_errorBuf->data);
		pfree(fc_errorBuf);
		return;
	}

	/*
	 * 我们不想在这里调用 ereport()，因为这可能会使 libxml 处于
	 * 不一致状态。相反，我们记住错误并从 xml_ereport() 中
	 * 调用 ereport()。
	 *
	 * 警告和通知可以立即报告，因为它们不会导致
	 * libxml 外的长跳转。
	 */
	if (fc_level >= XML_ERR_ERROR)
	{
		fc_appendStringInfoLineSeparator(&fc_xmlerrcxt->err_buf);
		appendBinaryStringInfo(&fc_xmlerrcxt->err_buf, fc_errorBuf->data,
							   fc_errorBuf->len);

		fc_xmlerrcxt->err_occurred = true;
	}
	else if (fc_level >= XML_ERR_WARNING)
	{
		ereport(WARNING,
				(errmsg_internal("%s", fc_errorBuf->data)));
	}
	else
	{
		ereport(NOTICE,
				(errmsg_internal("%s", fc_errorBuf->data)));
	}

	pfree(fc_errorBuf->data);
	pfree(fc_errorBuf);
}


/*
 * XML 相关错误的 "ereport" 函数的封装。 "msg"
 * 是 SQL 级别的消息；一些可以采纳自 SQL/XML
 * 标准。此函数使用 "code" 来创建文本详细
 * 消息。目前，我们只需要覆盖那些我们
 * 可能在此文件中引发的代码。
 */
static void fc_xml_ereport_by_code(int fc_level, int fc_sqlcode,
					const char *fc_msg, int fc_code)
{
	const char *fc_det;

	switch (fc_code)
	{
		case XML_ERR_INVALID_CHAR:
			fc_det = gettext_noop("Invalid character value.");
			break;
		case XML_ERR_SPACE_REQUIRED:
			fc_det = gettext_noop("Space required.");
			break;
		case XML_ERR_STANDALONE_VALUE:
			fc_det = gettext_noop("standalone accepts only 'yes' or 'no'.");
			break;
		case XML_ERR_VERSION_MISSING:
			fc_det = gettext_noop("Malformed declaration: missing version.");
			break;
		case XML_ERR_MISSING_ENCODING:
			fc_det = gettext_noop("Missing encoding in text declaration.");
			break;
		case XML_ERR_XMLDECL_NOT_FINISHED:
			fc_det = gettext_noop("Parsing XML declaration: '?>' expected.");
			break;
		default:
			fc_det = gettext_noop("Unrecognized libxml error code: %d.");
			break;
	}

	ereport(fc_level,
			(errcode(fc_sqlcode),
			 errmsg_internal("%s", fc_msg),
			 errdetail(fc_det, fc_code)));
}


/*
 * 从 StringInfo 字符串中删除所有尾部换行符
 */
static void fc_chopStringInfoNewlines(StringInfo fc_str)
{
	while (fc_str->len > 0 && fc_str->data[fc_str->len - 1] == '\n')
		fc_str->data[--fc_str->len] = '\0';
}


/*
 * 在删除任何现有的尾随换行符后附加一个换行符
 */
static void fc_appendStringInfoLineSeparator(StringInfo fc_str)
{
	fc_chopStringInfoNewlines(fc_str);
	if (fc_str->len > 0)
		appendStringInfoChar(fc_str, '\n');
}


/*
 * 将当前服务器编码中的一个字符转换为 Unicode 代码点。
 */
static pg_wchar fc_sqlchar_to_unicode(const char *fc_s)
{
	char	   *fc_utf8string;
	pg_wchar	fc_ret[2];			/* 需要空间用于尾随零 */

	/* 注意我们并不假设 s 是以 null 结尾的 */
	fc_utf8string = pg_server_to_any(fc_s, pg_mblen(fc_s), PG_UTF8);

	pg_encoding_mb2wchar_with_len(PG_UTF8, fc_utf8string, fc_ret,
								  pg_encoding_mblen(PG_UTF8, fc_utf8string));

	if (fc_utf8string != fc_s)
		pfree(fc_utf8string);

	return fc_ret[0];
}


static bool fc_is_valid_xml_namefirst(pg_wchar fc_c)
{
	/* （字母 | '_' | ':'） */
	return (xmlIsBaseCharQ(fc_c) || xmlIsIdeographicQ(fc_c)
			|| fc_c == '_' || fc_c == ':');
}


static bool fc_is_valid_xml_namechar(pg_wchar fc_c)
{
	/* 字母 | 数字 | '.' | '-' | '_' | ':' | 组合字符 | 扩展符 */
	return (xmlIsBaseCharQ(fc_c) || xmlIsIdeographicQ(fc_c)
			|| xmlIsDigitQ(fc_c)
			|| fc_c == '.' || fc_c == '-' || fc_c == '_' || fc_c == ':'
			|| xmlIsCombiningQ(fc_c)
			|| xmlIsExtenderQ(fc_c));
}
#endif							/* USE_LIBXML */


/*
 * 将 SQL 标识符映射到 XML 名称；见 SQL/XML:2008 第 9.1节。
 */
char * map_sql_identifier_to_xml_name(const char *fc_ident, bool fc_fully_escaped,
							   bool fc_escape_period)
{
#ifdef USE_LIBXML
	StringInfoData fc_buf;
	const char *fc_p;

	/*
	 * SQL/XML 在任何地方都没有使用这种情况，因此这可能是一个
	 * 错误。
	 */
	Assert(fc_fully_escaped || !fc_escape_period);

	initStringInfo(&fc_buf);

	for (fc_p = fc_ident; *fc_p; fc_p += pg_mblen(fc_p))
	{
		if (*fc_p == ':' && (fc_p == fc_ident || fc_fully_escaped))
			appendStringInfoString(&fc_buf, "_x003A_");
		else if (*fc_p == '_' && *(fc_p + 1) == 'x')
			appendStringInfoString(&fc_buf, "_x005F_");
		else if (fc_fully_escaped && fc_p == fc_ident &&
				 pg_strncasecmp(fc_p, "xml", 3) == 0)
		{
			if (*fc_p == 'x')
				appendStringInfoString(&fc_buf, "_x0078_");
			else
				appendStringInfoString(&fc_buf, "_x0058_");
		}
		else if (fc_escape_period && *fc_p == '.')
			appendStringInfoString(&fc_buf, "_x002E_");
		else
		{
			pg_wchar	fc_u = fc_sqlchar_to_unicode(fc_p);

			if ((fc_p == fc_ident)
				? !fc_is_valid_xml_namefirst(fc_u)
				: !fc_is_valid_xml_namechar(fc_u))
				appendStringInfo(&fc_buf, "_x%04X_", (unsigned int) fc_u);
			else
				appendBinaryStringInfo(&fc_buf, fc_p, pg_mblen(fc_p));
		}
	}

	return fc_buf.data;
#else							/* 非 USE_LIBXML */
	NO_XML_SUPPORT();
	return NULL;
#endif							/* 非 USE_LIBXML */
}


/*
 * 将 XML 名称映射到 SQL 标识符；见 SQL/XML:2008 第 9.3节。
 */
char * map_xml_name_to_sql_identifier(const char *fc_name)
{
	StringInfoData fc_buf;
	const char *fc_p;

	initStringInfo(&fc_buf);

	for (fc_p = fc_name; *fc_p; fc_p += pg_mblen(fc_p))
	{
		if (*fc_p == '_' && *(fc_p + 1) == 'x'
			&& isxdigit((unsigned char) *(fc_p + 2))
			&& isxdigit((unsigned char) *(fc_p + 3))
			&& isxdigit((unsigned char) *(fc_p + 4))
			&& isxdigit((unsigned char) *(fc_p + 5))
			&& *(fc_p + 6) == '_')
		{
			char		fc_cbuf[MAX_UNICODE_EQUIVALENT_STRING + 1];
			unsigned int fc_u;

			sscanf(fc_p + 2, "%X", &fc_u);
			pg_unicode_to_server(fc_u, (unsigned char *) fc_cbuf);
			appendStringInfoString(&fc_buf, fc_cbuf);
			fc_p += 6;
		}
		else
			appendBinaryStringInfo(&fc_buf, fc_p, pg_mblen(fc_p));
	}

	return fc_buf.data;
}

/*
 * 将 SQL 值映射到 XML 值；见 SQL/XML:2008 第 9.8节。
 *
 * 当 xml_escape_strings 为 true 时，字符串
 * 值中的某些字符会被实体引用 (&lt; 等) 替换，如
 * SQL/XML:2008 第 9.8节 GR 9) a) iii) 中所述。这通常是
 * 所需的。false 情况主要在结果值
 * 用于 xmlTextWriterWriteAttribute() 写出
 * 属性时有用，因为该函数本身会进行转义。
 */
char * map_sql_value_to_xml_value(Datum fc_value, Oid fc_type, bool fc_xml_escape_strings)
{
	if (type_is_array_domain(fc_type))
	{
		ArrayType  *fc_array;
		Oid			fc_elmtype;
		int16		fc_elmlen;
		bool		fc_elmbyval;
		char		fc_elmalign;
		int			fc_num_elems;
		Datum	   *fc_elem_values;
		bool	   *fc_elem_nulls;
		StringInfoData fc_buf;
		int			fc_i;

		fc_array = DatumGetArrayTypeP(fc_value);
		fc_elmtype = ARR_ELEMTYPE(fc_array);
		get_typlenbyvalalign(fc_elmtype, &fc_elmlen, &fc_elmbyval, &fc_elmalign);

		deconstruct_array(fc_array, fc_elmtype,
						  fc_elmlen, fc_elmbyval, fc_elmalign,
						  &fc_elem_values, &fc_elem_nulls,
						  &fc_num_elems);

		initStringInfo(&fc_buf);

		for (fc_i = 0; fc_i < fc_num_elems; fc_i++)
		{
			if (fc_elem_nulls[fc_i])
				continue;
			appendStringInfoString(&fc_buf, "<element>");
			appendStringInfoString(&fc_buf,
								   map_sql_value_to_xml_value(fc_elem_values[fc_i],
															  fc_elmtype, true));
			appendStringInfoString(&fc_buf, "</element>");
		}

		pfree(fc_elem_values);
		pfree(fc_elem_nulls);

		return fc_buf.data;
	}
	else
	{
		Oid			fc_typeOut;
		bool		fc_isvarlena;
		char	   *fc_str;

		/*
		 * 扁平化域；以下特殊情况的处理应适用于
		 * 例如，不仅仅是布尔的布尔域。
		 */
		fc_type = getBaseType(fc_type);

		/*
		 * 某些数据类型的特殊 XSD 格式
		 */
		switch (fc_type)
		{
			case BOOLOID:
				if (DatumGetBool(fc_value))
					return "true";
				else
					return "false";

			case DATEOID:
				{
					DateADT		fc_date;
					struct pg_tm fc_tm;
					char		fc_buf[MAXDATELEN + 1];

					fc_date = DatumGetDateADT(fc_value);
					/* XSD 不支持无限值 */
					if (DATE_NOT_FINITE(fc_date))
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("date out of range"),
								 errdetail("XML does not support infinite date values.")));
					j2date(fc_date + POSTGRES_EPOCH_JDATE,
						   &(fc_tm.tm_year), &(fc_tm.tm_mon), &(fc_tm.tm_mday));
					EncodeDateOnly(&fc_tm, USE_XSD_DATES, fc_buf);

					return pstrdup(fc_buf);
				}

			case TIMESTAMPOID:
				{
					Timestamp	fc_timestamp;
					struct pg_tm fc_tm;
					fsec_t		fc_fsec;
					char		fc_buf[MAXDATELEN + 1];

					fc_timestamp = DatumGetTimestamp(fc_value);

					/* XSD 不支持无限值 */
					if (TIMESTAMP_NOT_FINITE(fc_timestamp))
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("timestamp out of range"),
								 errdetail("XML does not support infinite timestamp values.")));
					else if (timestamp2tm(fc_timestamp, NULL, &fc_tm, &fc_fsec, NULL, NULL) == 0)
						EncodeDateTime(&fc_tm, fc_fsec, false, 0, NULL, USE_XSD_DATES, fc_buf);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("timestamp out of range")));

					return pstrdup(fc_buf);
				}

			case TIMESTAMPTZOID:
				{
					TimestampTz fc_timestamp;
					struct pg_tm fc_tm;
					int			fc_tz;
					fsec_t		fc_fsec;
					const char *fc_tzn = NULL;
					char		fc_buf[MAXDATELEN + 1];

					fc_timestamp = DatumGetTimestamp(fc_value);

					/* XSD 不支持无限值 */
					if (TIMESTAMP_NOT_FINITE(fc_timestamp))
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("timestamp out of range"),
								 errdetail("XML does not support infinite timestamp values.")));
					else if (timestamp2tm(fc_timestamp, &fc_tz, &fc_tm, &fc_fsec, &fc_tzn, NULL) == 0)
						EncodeDateTime(&fc_tm, fc_fsec, true, fc_tz, fc_tzn, USE_XSD_DATES, fc_buf);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("timestamp out of range")));

					return pstrdup(fc_buf);
				}

#ifdef USE_LIBXML
			case BYTEAOID:
				{
					bytea	   *fc_bstr = DatumGetByteaPP(fc_value);
					PgXmlErrorContext *fc_xmlerrcxt;
					volatile xmlBufferPtr fc_buf = NULL;
					volatile xmlTextWriterPtr fc_writer = NULL;
					char	   *fc_result;

					fc_xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);

					PG_TRY();
					{
						fc_buf = xmlBufferCreate();
						if (fc_buf == NULL || fc_xmlerrcxt->err_occurred)
							xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
										"could not allocate xmlBuffer");
						fc_writer = xmlNewTextWriterMemory(fc_buf, 0);
						if (fc_writer == NULL || fc_xmlerrcxt->err_occurred)
							xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
										"could not allocate xmlTextWriter");

						if (xmlbinary == XMLBINARY_BASE64)
							xmlTextWriterWriteBase64(fc_writer, VARDATA_ANY(fc_bstr),
													 0, VARSIZE_ANY_EXHDR(fc_bstr));
						else
							xmlTextWriterWriteBinHex(fc_writer, VARDATA_ANY(fc_bstr),
													 0, VARSIZE_ANY_EXHDR(fc_bstr));

						/* 我们现在必须这样做以将数据刷新到缓冲区 */
						xmlFreeTextWriter(fc_writer);
						fc_writer = NULL;

						fc_result = pstrdup((const char *) xmlBufferContent(fc_buf));
					}
					PG_CATCH();
					{
						if (fc_writer)
							xmlFreeTextWriter(fc_writer);
						if (fc_buf)
							xmlBufferFree(fc_buf);

						pg_xml_done(fc_xmlerrcxt, true);

						PG_RE_THROW();
					}
					PG_END_TRY();

					xmlBufferFree(fc_buf);

					pg_xml_done(fc_xmlerrcxt, false);

					return fc_result;
				}
#endif							/* USE_LIBXML */

		}

		/*
		 * 否则，仅使用类型的原生文本表示
		 */
		getTypeOutputInfo(fc_type, &fc_typeOut, &fc_isvarlena);
		fc_str = OidOutputFunctionCall(fc_typeOut, fc_value);

		/* ... 与XML完全相同，并且在不需要转义时 */
		if (fc_type == XMLOID || !fc_xml_escape_strings)
			return fc_str;

		/* 否则，根据需要翻译特殊字符 */
		return escape_xml(fc_str);
	}
}


/*
 * 转义在XML中具有特殊含义的文本字符。
 *
 * 返回一个palloc分配的字符串。
 *
 * 注意：这一点故意不依赖于libxml。
 */
char * escape_xml(const char *fc_str)
{
	StringInfoData fc_buf;
	const char *fc_p;

	initStringInfo(&fc_buf);
	for (fc_p = fc_str; *fc_p; fc_p++)
	{
		switch (*fc_p)
		{
			case '&':
				appendStringInfoString(&fc_buf, "&amp;");
				break;
			case '<':
				appendStringInfoString(&fc_buf, "&lt;");
				break;
			case '>':
				appendStringInfoString(&fc_buf, "&gt;");
				break;
			case '\r':
				appendStringInfoString(&fc_buf, "&#x0d;");
				break;
			default:
				appendStringInfoCharMacro(&fc_buf, *fc_p);
				break;
		}
	}
	return fc_buf.data;
}


static char * fc__SPI_strdup(const char *fc_s)
{
	size_t		fc_len = strlen(fc_s) + 1;
	char	   *fc_ret = SPI_palloc(fc_len);

	memcpy(fc_ret, fc_s, fc_len);
	return fc_ret;
}


/*
 * SQL到XML映射函数
 *
 * 下面的内容在某个时候故意组织，使您可以在SQL/XML标准中进行阅读。函数大致按照标准文档中条款的布局进行分组，标识符也与标准文本对齐。不幸的是，SQL/XML:2006重新排列了条款，与SQL/XML:2003不同，因此下面的顺序现在没有太大意义。
 *
 * 这里有许多事情在进行：
 *
 * 有两种映射：将SQL数据（表内容）映射到XML文档，以及将SQL结构（“架构”）映射到XML Schema。还有一些函数可以同时执行这两者。
 *
 * 然后您可以以两种方式映射数据库、架构或表。此过程递归分解：映射数据库会调用映射架构，后者会调用映射表，接着会调用映射行，然后再调用映射列，尽管您不能从外部调用最后两个。由于此原因，有许多xyz_internal()函数，既要从函数管理器包装器调用，也要从递归调用中的某个上层调用。
 *
 * 有关通用函数参数nulls、tableforest和targetns的含义，请参阅文档。
 *
 * XML输出的一些样式指南：使用双引号来引用XML属性。将XML元素缩进两个空格，但请记住，很多代码是以递归方式在不同级别上调用的，因此最好不要缩进，而不是产生奇怪的缩进和取消缩进的输出。添加换行符以使输出看起来美观。
 */


/*
 * XML映射中文件的可见性；参见SQL/XML:2008第4.10.8节。
 */

/*
 * 给定一个查询，该查询必须返回类型oid作为第一列，生成一个包含查询结果的Oid列表。
 */
static List * fc_query_to_oid_list(const char *fc_query)
{
	uint64		fc_i;
	List	   *fc_list = NIL;
	int			fc_spi_result;

	fc_spi_result = SPI_execute(fc_query, true, 0);
	if (fc_spi_result != SPI_OK_SELECT)
		elog(ERROR, "SPI_execute returned %s for %s",
			 SPI_result_code_string(fc_spi_result), fc_query);

	for (fc_i = 0; fc_i < SPI_processed; fc_i++)
	{
		Datum		fc_oid;
		bool		fc_isnull;

		fc_oid = SPI_getbinval(SPI_tuptable->vals[fc_i],
							SPI_tuptable->tupdesc,
							1,
							&fc_isnull);
		if (!fc_isnull)
			fc_list = lappend_oid(fc_list, DatumGetObjectId(fc_oid));
	}

	return fc_list;
}


static List * fc_schema_get_xml_visible_tables(Oid fc_nspid)
{
	StringInfoData fc_query;

	initStringInfo(&fc_query);
	appendStringInfo(&fc_query, "SELECT oid FROM pg_catalog.pg_class"
					 " WHERE relnamespace = %u AND relkind IN ("
					 CppAsString2(RELKIND_RELATION) ","
					 CppAsString2(RELKIND_MATVIEW) ","
					 CppAsString2(RELKIND_VIEW) ")"
					 " AND pg_catalog.has_table_privilege (oid, 'SELECT')"
					 " ORDER BY relname;", fc_nspid);

	return fc_query_to_oid_list(fc_query.data);
}


/*
 * 包括系统架构对于数据库映射可能没有用。
 */
#define XML_VISIBLE_SCHEMAS_EXCLUDE "(nspname ~ '^pg_' OR nspname = 'information_schema')"

#define XML_VISIBLE_SCHEMAS "SELECT oid FROM pg_catalog.pg_namespace WHERE pg_catalog.has_schema_privilege (oid, 'USAGE') AND NOT " XML_VISIBLE_SCHEMAS_EXCLUDE


static List * fc_database_get_xml_visible_schemas(void)
{
	return fc_query_to_oid_list(XML_VISIBLE_SCHEMAS " ORDER BY nspname;");
}


static List * fc_database_get_xml_visible_tables(void)
{
	/* 目前这里没有要求的顺序。 */
	return fc_query_to_oid_list("SELECT oid FROM pg_catalog.pg_class"
							 " WHERE relkind IN ("
							 CppAsString2(RELKIND_RELATION) ","
							 CppAsString2(RELKIND_MATVIEW) ","
							 CppAsString2(RELKIND_VIEW) ")"
							 " AND pg_catalog.has_table_privilege(pg_class.oid, 'SELECT')"
							 " AND relnamespace IN (" XML_VISIBLE_SCHEMAS ");");
}


/*
 * 将SQL表映射到XML和/或XML Schema文档；参见SQL/XML:2008第9.11节。
 */

static StringInfo fc_table_to_xml_internal(Oid fc_relid,
					  const char *fc_xmlschema, bool fc_nulls, bool fc_tableforest,
					  const char *fc_targetns, bool fc_top_level)
{
	StringInfoData fc_query;

	initStringInfo(&fc_query);
	appendStringInfo(&fc_query, "SELECT * FROM %s",
					 DatumGetCString(DirectFunctionCall1(regclassout,
														 ObjectIdGetDatum(fc_relid))));
	return fc_query_to_xml_internal(fc_query.data, get_rel_name(fc_relid),
								 fc_xmlschema, fc_nulls, fc_tableforest,
								 fc_targetns, fc_top_level);
}


Datum table_to_xml(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	bool		fc_nulls = PG_GETARG_BOOL(1);
	bool		fc_tableforest = PG_GETARG_BOOL(2);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(fc_table_to_xml_internal(fc_relid, NULL,
																fc_nulls, fc_tableforest,
																fc_targetns, true)));
}


Datum query_to_xml(PG_FUNCTION_ARGS)
{
	char	   *fc_query = text_to_cstring(PG_GETARG_TEXT_PP(0));
	bool		fc_nulls = PG_GETARG_BOOL(1);
	bool		fc_tableforest = PG_GETARG_BOOL(2);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(fc_query_to_xml_internal(fc_query, NULL,
																NULL, fc_nulls, fc_tableforest,
																fc_targetns, true)));
}


Datum cursor_to_xml(PG_FUNCTION_ARGS)
{
	char	   *fc_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
	int32		fc_count = PG_GETARG_INT32(1);
	bool		fc_nulls = PG_GETARG_BOOL(2);
	bool		fc_tableforest = PG_GETARG_BOOL(3);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(4));

	StringInfoData fc_result;
	Portal		fc_portal;
	uint64		fc_i;

	initStringInfo(&fc_result);

	if (!fc_tableforest)
	{
		fc_xmldata_root_element_start(&fc_result, "table", NULL, fc_targetns, true);
		appendStringInfoChar(&fc_result, '\n');
	}

	SPI_connect();
	fc_portal = SPI_cursor_find(fc_name);
	if (fc_portal == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_CURSOR),
				 errmsg("cursor \"%s\" does not exist", fc_name)));

	SPI_cursor_fetch(fc_portal, true, fc_count);
	for (fc_i = 0; fc_i < SPI_processed; fc_i++)
		fc_SPI_sql_row_to_xmlelement(fc_i, &fc_result, NULL, fc_nulls,
								  fc_tableforest, fc_targetns, true);

	SPI_finish();

	if (!fc_tableforest)
		fc_xmldata_root_element_end(&fc_result, "table");

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(&fc_result));
}


/*
 * 写入数据映射的根元素的开始标签。
 *
 * top_level意味着这是最终输出的最顶层。例如，当用户调用table_to_xml时，则调用此函数的表名是顶层。当用户调用database_to_xml时，则调用此函数的架构名称不是顶层。如果top_level为false，则省略XML命名空间声明，因为它们应该已经在输出中出现。重复它们并不是错误的，但看起来很丑。
 */
static void fc_xmldata_root_element_start(StringInfo fc_result, const char *fc_eltname,
						   const char *fc_xmlschema, const char *fc_targetns,
						   bool fc_top_level)
{
	/* 这并不是真的错误，但目前没有意义。 */
	Assert(fc_top_level || !fc_xmlschema);

	appendStringInfo(fc_result, "<%s", fc_eltname);
	if (fc_top_level)
	{
		appendStringInfoString(fc_result, " xmlns:xsi=\"" NAMESPACE_XSI "\"");
		if (strlen(fc_targetns) > 0)
			appendStringInfo(fc_result, " xmlns=\"%s\"", fc_targetns);
	}
	if (fc_xmlschema)
	{
		/* FIXME: 更好的目标 */
		if (strlen(fc_targetns) > 0)
			appendStringInfo(fc_result, " xsi:schemaLocation=\"%s #\"", fc_targetns);
		else
			appendStringInfoString(fc_result, " xsi:noNamespaceSchemaLocation=\"#\"");
	}
	appendStringInfoString(fc_result, ">\n");
}


static void fc_xmldata_root_element_end(StringInfo fc_result, const char *fc_eltname)
{
	appendStringInfo(fc_result, "</%s>\n", fc_eltname);
}


static StringInfo fc_query_to_xml_internal(const char *fc_query, char *fc_tablename,
					  const char *fc_xmlschema, bool fc_nulls, bool fc_tableforest,
					  const char *fc_targetns, bool fc_top_level)
{
	StringInfo	fc_result;
	char	   *fc_xmltn;
	uint64		fc_i;

	if (fc_tablename)
		fc_xmltn = map_sql_identifier_to_xml_name(fc_tablename, true, false);
	else
		fc_xmltn = "table";

	fc_result = makeStringInfo();

	SPI_connect();
	if (SPI_execute(fc_query, true, 0) != SPI_OK_SELECT)
		ereport(ERROR,
				(errcode(ERRCODE_DATA_EXCEPTION),
				 errmsg("invalid query")));

	if (!fc_tableforest)
	{
		fc_xmldata_root_element_start(fc_result, fc_xmltn, fc_xmlschema,
								   fc_targetns, fc_top_level);
		appendStringInfoChar(fc_result, '\n');
	}

	if (fc_xmlschema)
		appendStringInfo(fc_result, "%s\n\n", fc_xmlschema);

	for (fc_i = 0; fc_i < SPI_processed; fc_i++)
		fc_SPI_sql_row_to_xmlelement(fc_i, fc_result, fc_tablename, fc_nulls,
								  fc_tableforest, fc_targetns, fc_top_level);

	if (!fc_tableforest)
		fc_xmldata_root_element_end(fc_result, fc_xmltn);

	SPI_finish();

	return fc_result;
}


Datum table_to_xmlschema(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	bool		fc_nulls = PG_GETARG_BOOL(1);
	bool		fc_tableforest = PG_GETARG_BOOL(2);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
	const char *fc_result;
	Relation	fc_rel;

	fc_rel = table_open(fc_relid, AccessShareLock);
	fc_result = fc_map_sql_table_to_xmlschema(fc_rel->rd_att, fc_relid, fc_nulls,
										fc_tableforest, fc_targetns);
	table_close(fc_rel, NoLock);

	PG_RETURN_XML_P(fc_cstring_to_xmltype(fc_result));
}


Datum query_to_xmlschema(PG_FUNCTION_ARGS)
{
	char	   *fc_query = text_to_cstring(PG_GETARG_TEXT_PP(0));
	bool		fc_nulls = PG_GETARG_BOOL(1);
	bool		fc_tableforest = PG_GETARG_BOOL(2);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
	const char *fc_result;
	SPIPlanPtr	fc_plan;
	Portal		fc_portal;

	SPI_connect();

	if ((fc_plan = SPI_prepare(fc_query, 0, NULL)) == NULL)
		elog(ERROR, "SPI_prepare(\"%s\") failed", fc_query);

	if ((fc_portal = SPI_cursor_open(NULL, fc_plan, NULL, NULL, true)) == NULL)
		elog(ERROR, "SPI_cursor_open(\"%s\") failed", fc_query);

	fc_result = fc__SPI_strdup(fc_map_sql_table_to_xmlschema(fc_portal->tupDesc,
													InvalidOid, fc_nulls,
													fc_tableforest, fc_targetns));
	SPI_cursor_close(fc_portal);
	SPI_finish();

	PG_RETURN_XML_P(fc_cstring_to_xmltype(fc_result));
}


Datum cursor_to_xmlschema(PG_FUNCTION_ARGS)
{
	char	   *fc_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
	bool		fc_nulls = PG_GETARG_BOOL(1);
	bool		fc_tableforest = PG_GETARG_BOOL(2);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
	const char *fc_xmlschema;
	Portal		fc_portal;

	SPI_connect();
	fc_portal = SPI_cursor_find(fc_name);
	if (fc_portal == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_CURSOR),
				 errmsg("cursor \"%s\" does not exist", fc_name)));
	if (fc_portal->tupDesc == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_CURSOR_STATE),
				 errmsg("portal \"%s\" does not return tuples", fc_name)));

	fc_xmlschema = fc__SPI_strdup(fc_map_sql_table_to_xmlschema(fc_portal->tupDesc,
													   InvalidOid, fc_nulls,
													   fc_tableforest, fc_targetns));
	SPI_finish();

	PG_RETURN_XML_P(fc_cstring_to_xmltype(fc_xmlschema));
}


Datum table_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	bool		fc_nulls = PG_GETARG_BOOL(1);
	bool		fc_tableforest = PG_GETARG_BOOL(2);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
	Relation	fc_rel;
	const char *fc_xmlschema;

	fc_rel = table_open(fc_relid, AccessShareLock);
	fc_xmlschema = fc_map_sql_table_to_xmlschema(fc_rel->rd_att, fc_relid, fc_nulls,
										   fc_tableforest, fc_targetns);
	table_close(fc_rel, NoLock);

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(fc_table_to_xml_internal(fc_relid,
																fc_xmlschema, fc_nulls, fc_tableforest,
																fc_targetns, true)));
}


Datum query_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
{
	char	   *fc_query = text_to_cstring(PG_GETARG_TEXT_PP(0));
	bool		fc_nulls = PG_GETARG_BOOL(1);
	bool		fc_tableforest = PG_GETARG_BOOL(2);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));

	const char *fc_xmlschema;
	SPIPlanPtr	fc_plan;
	Portal		fc_portal;

	SPI_connect();

	if ((fc_plan = SPI_prepare(fc_query, 0, NULL)) == NULL)
		elog(ERROR, "SPI_prepare(\"%s\") failed", fc_query);

	if ((fc_portal = SPI_cursor_open(NULL, fc_plan, NULL, NULL, true)) == NULL)
		elog(ERROR, "SPI_cursor_open(\"%s\") failed", fc_query);

	fc_xmlschema = fc__SPI_strdup(fc_map_sql_table_to_xmlschema(fc_portal->tupDesc,
													   InvalidOid, fc_nulls, fc_tableforest, fc_targetns));
	SPI_cursor_close(fc_portal);
	SPI_finish();

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(fc_query_to_xml_internal(fc_query, NULL,
																fc_xmlschema, fc_nulls, fc_tableforest,
																fc_targetns, true)));
}


/*
 * 将SQL架构映射到XML和/或XML Schema文档；参见SQL/XML:2008第9.13、9.14节。
 */

static StringInfo fc_schema_to_xml_internal(Oid fc_nspid, const char *fc_xmlschema, bool fc_nulls,
					   bool fc_tableforest, const char *fc_targetns, bool fc_top_level)
{
	StringInfo	fc_result;
	char	   *fc_xmlsn;
	List	   *fc_relid_list;
	ListCell   *fc_cell;

	fc_xmlsn = map_sql_identifier_to_xml_name(get_namespace_name(fc_nspid),
										   true, false);
	fc_result = makeStringInfo();

	fc_xmldata_root_element_start(fc_result, fc_xmlsn, fc_xmlschema, fc_targetns, fc_top_level);
	appendStringInfoChar(fc_result, '\n');

	if (fc_xmlschema)
		appendStringInfo(fc_result, "%s\n\n", fc_xmlschema);

	SPI_connect();

	fc_relid_list = fc_schema_get_xml_visible_tables(fc_nspid);

	foreach(fc_cell, fc_relid_list)
	{
		Oid			fc_relid = lfirst_oid(fc_cell);
		StringInfo	fc_subres;

		fc_subres = fc_table_to_xml_internal(fc_relid, NULL, fc_nulls, fc_tableforest,
									   fc_targetns, false);

		appendBinaryStringInfo(fc_result, fc_subres->data, fc_subres->len);
		appendStringInfoChar(fc_result, '\n');
	}

	SPI_finish();

	fc_xmldata_root_element_end(fc_result, fc_xmlsn);

	return fc_result;
}


Datum schema_to_xml(PG_FUNCTION_ARGS)
{
	Name		fc_name = PG_GETARG_NAME(0);
	bool		fc_nulls = PG_GETARG_BOOL(1);
	bool		fc_tableforest = PG_GETARG_BOOL(2);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));

	char	   *fc_schemaname;
	Oid			fc_nspid;

	fc_schemaname = NameStr(*fc_name);
	fc_nspid = LookupExplicitNamespace(fc_schemaname, false);

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(fc_schema_to_xml_internal(fc_nspid, NULL,
																 fc_nulls, fc_tableforest, fc_targetns, true)));
}


/*
 * 写入XML Schema映射的根元素的开始元素。
 */
static void fc_xsd_schema_element_start(StringInfo fc_result, const char *fc_targetns)
{
	appendStringInfoString(fc_result,
						   "<xsd:schema\n"
						   "    xmlns:xsd=\"" NAMESPACE_XSD "\"");
	if (strlen(fc_targetns) > 0)
		appendStringInfo(fc_result,
						 "\n"
						 "    targetNamespace=\"%s\"\n"
						 "    elementFormDefault=\"qualified\"",
						 fc_targetns);
	appendStringInfoString(fc_result,
						   ">\n\n");
}


static void fc_xsd_schema_element_end(StringInfo fc_result)
{
	appendStringInfoString(fc_result, "</xsd:schema>");
}


static StringInfo fc_schema_to_xmlschema_internal(const char *fc_schemaname, bool fc_nulls,
							 bool fc_tableforest, const char *fc_targetns)
{
	Oid			fc_nspid;
	List	   *fc_relid_list;
	List	   *fc_tupdesc_list;
	ListCell   *fc_cell;
	StringInfo	fc_result;

	fc_result = makeStringInfo();

	fc_nspid = LookupExplicitNamespace(fc_schemaname, false);

	fc_xsd_schema_element_start(fc_result, fc_targetns);

	SPI_connect();

	fc_relid_list = fc_schema_get_xml_visible_tables(fc_nspid);

	fc_tupdesc_list = NIL;
	foreach(fc_cell, fc_relid_list)
	{
		Relation	fc_rel;

		fc_rel = table_open(lfirst_oid(fc_cell), AccessShareLock);
		fc_tupdesc_list = lappend(fc_tupdesc_list, CreateTupleDescCopy(fc_rel->rd_att));
		table_close(fc_rel, NoLock);
	}

	appendStringInfoString(fc_result,
						   fc_map_sql_typecoll_to_xmlschema_types(fc_tupdesc_list));

	appendStringInfoString(fc_result,
						   fc_map_sql_schema_to_xmlschema_types(fc_nspid, fc_relid_list,
															 fc_nulls, fc_tableforest, fc_targetns));

	fc_xsd_schema_element_end(fc_result);

	SPI_finish();

	return fc_result;
}


Datum schema_to_xmlschema(PG_FUNCTION_ARGS)
{
	Name		fc_name = PG_GETARG_NAME(0);
	bool		fc_nulls = PG_GETARG_BOOL(1);
	bool		fc_tableforest = PG_GETARG_BOOL(2);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(fc_schema_to_xmlschema_internal(NameStr(*fc_name),
																	   fc_nulls, fc_tableforest, fc_targetns)));
}


Datum schema_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
{
	Name		fc_name = PG_GETARG_NAME(0);
	bool		fc_nulls = PG_GETARG_BOOL(1);
	bool		fc_tableforest = PG_GETARG_BOOL(2);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
	char	   *fc_schemaname;
	Oid			fc_nspid;
	StringInfo	fc_xmlschema;

	fc_schemaname = NameStr(*fc_name);
	fc_nspid = LookupExplicitNamespace(fc_schemaname, false);

	fc_xmlschema = fc_schema_to_xmlschema_internal(fc_schemaname, fc_nulls,
											 fc_tableforest, fc_targetns);

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(fc_schema_to_xml_internal(fc_nspid,
																 fc_xmlschema->data, fc_nulls,
																 fc_tableforest, fc_targetns, true)));
}


/*
 * 将SQL数据库映射到XML和/或XML Schema文档；参见SQL/XML:2008第9.16、9.17节。
 */

static StringInfo fc_database_to_xml_internal(const char *fc_xmlschema, bool fc_nulls,
						 bool fc_tableforest, const char *fc_targetns)
{
	StringInfo	fc_result;
	List	   *fc_nspid_list;
	ListCell   *fc_cell;
	char	   *fc_xmlcn;

	fc_xmlcn = map_sql_identifier_to_xml_name(get_database_name(MyDatabaseId),
										   true, false);
	fc_result = makeStringInfo();

	fc_xmldata_root_element_start(fc_result, fc_xmlcn, fc_xmlschema, fc_targetns, true);
	appendStringInfoChar(fc_result, '\n');

	if (fc_xmlschema)
		appendStringInfo(fc_result, "%s\n\n", fc_xmlschema);

	SPI_connect();

	fc_nspid_list = fc_database_get_xml_visible_schemas();

	foreach(fc_cell, fc_nspid_list)
	{
		Oid			fc_nspid = lfirst_oid(fc_cell);
		StringInfo	fc_subres;

		fc_subres = fc_schema_to_xml_internal(fc_nspid, NULL, fc_nulls,
										fc_tableforest, fc_targetns, false);

		appendBinaryStringInfo(fc_result, fc_subres->data, fc_subres->len);
		appendStringInfoChar(fc_result, '\n');
	}

	SPI_finish();

	fc_xmldata_root_element_end(fc_result, fc_xmlcn);

	return fc_result;
}


Datum database_to_xml(PG_FUNCTION_ARGS)
{
	bool		fc_nulls = PG_GETARG_BOOL(0);
	bool		fc_tableforest = PG_GETARG_BOOL(1);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(2));

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(fc_database_to_xml_internal(NULL, fc_nulls,
																   fc_tableforest, fc_targetns)));
}


static StringInfo fc_database_to_xmlschema_internal(bool fc_nulls, bool fc_tableforest,
							   const char *fc_targetns)
{
	List	   *fc_relid_list;
	List	   *fc_nspid_list;
	List	   *fc_tupdesc_list;
	ListCell   *fc_cell;
	StringInfo	fc_result;

	fc_result = makeStringInfo();

	fc_xsd_schema_element_start(fc_result, fc_targetns);

	SPI_connect();

	fc_relid_list = fc_database_get_xml_visible_tables();
	fc_nspid_list = fc_database_get_xml_visible_schemas();

	fc_tupdesc_list = NIL;
	foreach(fc_cell, fc_relid_list)
	{
		Relation	fc_rel;

		fc_rel = table_open(lfirst_oid(fc_cell), AccessShareLock);
		fc_tupdesc_list = lappend(fc_tupdesc_list, CreateTupleDescCopy(fc_rel->rd_att));
		table_close(fc_rel, NoLock);
	}

	appendStringInfoString(fc_result,
						   fc_map_sql_typecoll_to_xmlschema_types(fc_tupdesc_list));

	appendStringInfoString(fc_result,
						   fc_map_sql_catalog_to_xmlschema_types(fc_nspid_list, fc_nulls, fc_tableforest, fc_targetns));

	fc_xsd_schema_element_end(fc_result);

	SPI_finish();

	return fc_result;
}


Datum database_to_xmlschema(PG_FUNCTION_ARGS)
{
	bool		fc_nulls = PG_GETARG_BOOL(0);
	bool		fc_tableforest = PG_GETARG_BOOL(1);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(2));

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(fc_database_to_xmlschema_internal(fc_nulls,
																		 fc_tableforest, fc_targetns)));
}


Datum database_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
{
	bool		fc_nulls = PG_GETARG_BOOL(0);
	bool		fc_tableforest = PG_GETARG_BOOL(1);
	const char *fc_targetns = text_to_cstring(PG_GETARG_TEXT_PP(2));
	StringInfo	fc_xmlschema;

	fc_xmlschema = fc_database_to_xmlschema_internal(fc_nulls, fc_tableforest, fc_targetns);

	PG_RETURN_XML_P(fc_stringinfo_to_xmltype(fc_database_to_xml_internal(fc_xmlschema->data,
																   fc_nulls, fc_tableforest, fc_targetns)));
}


/*
 * 将多部分SQL名称映射到XML名称；参见SQL/XML:2008第9.2节。
 */
static char * fc_map_multipart_sql_identifier_to_xml_name(const char *fc_a, const char *fc_b, const char *fc_c, const char *fc_d)
{
	StringInfoData fc_result;

	initStringInfo(&fc_result);

	if (fc_a)
		appendStringInfoString(&fc_result,
							   map_sql_identifier_to_xml_name(fc_a, true, true));
	if (fc_b)
		appendStringInfo(&fc_result, ".%s",
						 map_sql_identifier_to_xml_name(fc_b, true, true));
	if (fc_c)
		appendStringInfo(&fc_result, ".%s",
						 map_sql_identifier_to_xml_name(fc_c, true, true));
	if (fc_d)
		appendStringInfo(&fc_result, ".%s",
						 map_sql_identifier_to_xml_name(fc_d, true, true));

	return fc_result.data;
}


/*
 * 将SQL表映射到XML Schema文档；参见SQL/XML:2008第9.11节。
 *
 * 将SQL表映射到XML Schema数据类型；参见SQL/XML:2008第9.9节。
 */
static const char * fc_map_sql_table_to_xmlschema(TupleDesc fc_tupdesc, Oid fc_relid, bool fc_nulls,
						   bool fc_tableforest, const char *fc_targetns)
{
	int			fc_i;
	char	   *fc_xmltn;
	char	   *fc_tabletypename;
	char	   *fc_rowtypename;
	StringInfoData fc_result;

	initStringInfo(&fc_result);

	if (OidIsValid(fc_relid))
	{
		HeapTuple	fc_tuple;
		Form_pg_class fc_reltuple;

		fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
		if (!HeapTupleIsValid(fc_tuple))
			elog(ERROR, "cache lookup failed for relation %u", fc_relid);
		fc_reltuple = (Form_pg_class) GETSTRUCT(fc_tuple);

		fc_xmltn = map_sql_identifier_to_xml_name(NameStr(fc_reltuple->relname),
											   true, false);

		fc_tabletypename = fc_map_multipart_sql_identifier_to_xml_name("TableType",
																 get_database_name(MyDatabaseId),
																 get_namespace_name(fc_reltuple->relnamespace),
																 NameStr(fc_reltuple->relname));

		fc_rowtypename = fc_map_multipart_sql_identifier_to_xml_name("RowType",
															   get_database_name(MyDatabaseId),
															   get_namespace_name(fc_reltuple->relnamespace),
															   NameStr(fc_reltuple->relname));

		ReleaseSysCache(fc_tuple);
	}
	else
	{
		if (fc_tableforest)
			fc_xmltn = "row";
		else
			fc_xmltn = "table";

		fc_tabletypename = "TableType";
		fc_rowtypename = "RowType";
	}

	fc_xsd_schema_element_start(&fc_result, fc_targetns);

	appendStringInfoString(&fc_result,
						   fc_map_sql_typecoll_to_xmlschema_types(list_make1(fc_tupdesc)));

	appendStringInfo(&fc_result,
					 "<xsd:complexType name=\"%s\">\n"
					 "  <xsd:sequence>\n",
					 fc_rowtypename);

	for (fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);

		if (fc_att->attisdropped)
			continue;
		appendStringInfo(&fc_result,
						 "    <xsd:element name=\"%s\" type=\"%s\"%s></xsd:element>\n",
						 map_sql_identifier_to_xml_name(NameStr(fc_att->attname),
														true, false),
						 fc_map_sql_type_to_xml_name(fc_att->atttypid, -1),
						 fc_nulls ? " nillable=\"true\"" : " minOccurs=\"0\"");
	}

	appendStringInfoString(&fc_result,
						   "  </xsd:sequence>\n"
						   "</xsd:complexType>\n\n");

	if (!fc_tableforest)
	{
		appendStringInfo(&fc_result,
						 "<xsd:complexType name=\"%s\">\n"
						 "  <xsd:sequence>\n"
						 "    <xsd:element name=\"row\" type=\"%s\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n"
						 "  </xsd:sequence>\n"
						 "</xsd:complexType>\n\n",
						 fc_tabletypename, fc_rowtypename);

		appendStringInfo(&fc_result,
						 "<xsd:element name=\"%s\" type=\"%s\"/>\n\n",
						 fc_xmltn, fc_tabletypename);
	}
	else
		appendStringInfo(&fc_result,
						 "<xsd:element name=\"%s\" type=\"%s\"/>\n\n",
						 fc_xmltn, fc_rowtypename);

	fc_xsd_schema_element_end(&fc_result);

	return fc_result.data;
}


/*
 * 将SQL架构映射到XML Schema数据类型；参见SQL/XML:2008第9.12节。
 */
static const char * fc_map_sql_schema_to_xmlschema_types(Oid fc_nspid, List *fc_relid_list, bool fc_nulls,
								  bool fc_tableforest, const char *fc_targetns)
{
	char	   *fc_dbname;
	char	   *fc_nspname;
	char	   *fc_xmlsn;
	char	   *fc_schematypename;
	StringInfoData fc_result;
	ListCell   *fc_cell;

	fc_dbname = get_database_name(MyDatabaseId);
	fc_nspname = get_namespace_name(fc_nspid);

	initStringInfo(&fc_result);

	fc_xmlsn = map_sql_identifier_to_xml_name(fc_nspname, true, false);

	fc_schematypename = fc_map_multipart_sql_identifier_to_xml_name("SchemaType",
															  fc_dbname,
															  fc_nspname,
															  NULL);

	appendStringInfo(&fc_result,
					 "<xsd:complexType name=\"%s\">\n", fc_schematypename);
	if (!fc_tableforest)
		appendStringInfoString(&fc_result,
							   "  <xsd:all>\n");
	else
		appendStringInfoString(&fc_result,
							   "  <xsd:sequence>\n");

	foreach(fc_cell, fc_relid_list)
	{
		Oid			fc_relid = lfirst_oid(fc_cell);
		char	   *fc_relname = get_rel_name(fc_relid);
		char	   *fc_xmltn = map_sql_identifier_to_xml_name(fc_relname, true, false);
		char	   *fc_tabletypename = fc_map_multipart_sql_identifier_to_xml_name(fc_tableforest ? "RowType" : "TableType",
																			 fc_dbname,
																			 fc_nspname,
																			 fc_relname);

		if (!fc_tableforest)
			appendStringInfo(&fc_result,
							 "    <xsd:element name=\"%s\" type=\"%s\"/>\n",
							 fc_xmltn, fc_tabletypename);
		else
			appendStringInfo(&fc_result,
							 "    <xsd:element name=\"%s\" type=\"%s\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n",
							 fc_xmltn, fc_tabletypename);
	}

	if (!fc_tableforest)
		appendStringInfoString(&fc_result,
							   "  </xsd:all>\n");
	else
		appendStringInfoString(&fc_result,
							   "  </xsd:sequence>\n");
	appendStringInfoString(&fc_result,
						   "</xsd:complexType>\n\n");

	appendStringInfo(&fc_result,
					 "<xsd:element name=\"%s\" type=\"%s\"/>\n\n",
					 fc_xmlsn, fc_schematypename);

	return fc_result.data;
}


/*
 * 将SQL目录映射到XML Schema数据类型；参见SQL/XML:2008第9.15节。
 */
static const char * fc_map_sql_catalog_to_xmlschema_types(List *fc_nspid_list, bool fc_nulls,
								   bool fc_tableforest, const char *fc_targetns)
{
	char	   *fc_dbname;
	char	   *fc_xmlcn;
	char	   *fc_catalogtypename;
	StringInfoData fc_result;
	ListCell   *fc_cell;

	fc_dbname = get_database_name(MyDatabaseId);

	initStringInfo(&fc_result);

	fc_xmlcn = map_sql_identifier_to_xml_name(fc_dbname, true, false);

	fc_catalogtypename = fc_map_multipart_sql_identifier_to_xml_name("CatalogType",
															   fc_dbname,
															   NULL,
															   NULL);

	appendStringInfo(&fc_result,
					 "<xsd:complexType name=\"%s\">\n", fc_catalogtypename);
	appendStringInfoString(&fc_result,
						   "  <xsd:all>\n");

	foreach(fc_cell, fc_nspid_list)
	{
		Oid			fc_nspid = lfirst_oid(fc_cell);
		char	   *fc_nspname = get_namespace_name(fc_nspid);
		char	   *fc_xmlsn = map_sql_identifier_to_xml_name(fc_nspname, true, false);
		char	   *fc_schematypename = fc_map_multipart_sql_identifier_to_xml_name("SchemaType",
																			  fc_dbname,
																			  fc_nspname,
																			  NULL);

		appendStringInfo(&fc_result,
						 "    <xsd:element name=\"%s\" type=\"%s\"/>\n",
						 fc_xmlsn, fc_schematypename);
	}

	appendStringInfoString(&fc_result,
						   "  </xsd:all>\n");
	appendStringInfoString(&fc_result,
						   "</xsd:complexType>\n\n");

	appendStringInfo(&fc_result,
					 "<xsd:element name=\"%s\" type=\"%s\"/>\n\n",
					 fc_xmlcn, fc_catalogtypename);

	return fc_result.data;
}


/*
 * 将SQL数据类型映射到XML名称；参见SQL/XML:2008第9.4节。
 */
static const char * fc_map_sql_type_to_xml_name(Oid fc_typeoid, int fc_typmod)
{
	StringInfoData fc_result;

	initStringInfo(&fc_result);

	switch (fc_typeoid)
	{
		case BPCHAROID:
			if (fc_typmod == -1)
				appendStringInfoString(&fc_result, "CHAR");
			else
				appendStringInfo(&fc_result, "CHAR_%d", fc_typmod - VARHDRSZ);
			break;
		case VARCHAROID:
			if (fc_typmod == -1)
				appendStringInfoString(&fc_result, "VARCHAR");
			else
				appendStringInfo(&fc_result, "VARCHAR_%d", fc_typmod - VARHDRSZ);
			break;
		case NUMERICOID:
			if (fc_typmod == -1)
				appendStringInfoString(&fc_result, "NUMERIC");
			else
				appendStringInfo(&fc_result, "NUMERIC_%d_%d",
								 ((fc_typmod - VARHDRSZ) >> 16) & 0xffff,
								 (fc_typmod - VARHDRSZ) & 0xffff);
			break;
		case INT4OID:
			appendStringInfoString(&fc_result, "INTEGER");
			break;
		case INT2OID:
			appendStringInfoString(&fc_result, "SMALLINT");
			break;
		case INT8OID:
			appendStringInfoString(&fc_result, "BIGINT");
			break;
		case FLOAT4OID:
			appendStringInfoString(&fc_result, "REAL");
			break;
		case FLOAT8OID:
			appendStringInfoString(&fc_result, "DOUBLE");
			break;
		case BOOLOID:
			appendStringInfoString(&fc_result, "BOOLEAN");
			break;
		case TIMEOID:
			if (fc_typmod == -1)
				appendStringInfoString(&fc_result, "TIME");
			else
				appendStringInfo(&fc_result, "TIME_%d", fc_typmod);
			break;
		case TIMETZOID:
			if (fc_typmod == -1)
				appendStringInfoString(&fc_result, "TIME_WTZ");
			else
				appendStringInfo(&fc_result, "TIME_WTZ_%d", fc_typmod);
			break;
		case TIMESTAMPOID:
			if (fc_typmod == -1)
				appendStringInfoString(&fc_result, "TIMESTAMP");
			else
				appendStringInfo(&fc_result, "TIMESTAMP_%d", fc_typmod);
			break;
		case TIMESTAMPTZOID:
			if (fc_typmod == -1)
				appendStringInfoString(&fc_result, "TIMESTAMP_WTZ");
			else
				appendStringInfo(&fc_result, "TIMESTAMP_WTZ_%d", fc_typmod);
			break;
		case DATEOID:
			appendStringInfoString(&fc_result, "DATE");
			break;
		case XMLOID:
			appendStringInfoString(&fc_result, "XML");
			break;
		default:
			{
				HeapTuple	fc_tuple;
				Form_pg_type fc_typtuple;

				fc_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_typeoid));
				if (!HeapTupleIsValid(fc_tuple))
					elog(ERROR, "cache lookup failed for type %u", fc_typeoid);
				fc_typtuple = (Form_pg_type) GETSTRUCT(fc_tuple);

				appendStringInfoString(&fc_result,
									   fc_map_multipart_sql_identifier_to_xml_name((fc_typtuple->typtype == TYPTYPE_DOMAIN) ? "Domain" : "UDT",
																				get_database_name(MyDatabaseId),
																				get_namespace_name(fc_typtuple->typnamespace),
																				NameStr(fc_typtuple->typname)));

				ReleaseSysCache(fc_tuple);
			}
	}

	return fc_result.data;
}


/*
 * 将一组SQL数据类型映射到XML Schema数据类型；参见SQL/XML:2008第9.7节。
 */
static const char * fc_map_sql_typecoll_to_xmlschema_types(List *fc_tupdesc_list)
{
	List	   *fc_uniquetypes = NIL;
	int			fc_i;
	StringInfoData fc_result;
	ListCell   *fc_cell0;

	/* 提取在TupleDescs集中的所有列类型 */
	foreach(fc_cell0, fc_tupdesc_list)
	{
		TupleDesc	fc_tupdesc = (TupleDesc) lfirst(fc_cell0);

		for (fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
		{
			Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);

			if (fc_att->attisdropped)
				continue;
			fc_uniquetypes = list_append_unique_oid(fc_uniquetypes, fc_att->atttypid);
		}
	}

	/* 添加领域的基本类型 */
	foreach(fc_cell0, fc_uniquetypes)
	{
		Oid			fc_typid = lfirst_oid(fc_cell0);
		Oid			fc_basetypid = getBaseType(fc_typid);

		if (fc_basetypid != fc_typid)
			fc_uniquetypes = list_append_unique_oid(fc_uniquetypes, fc_basetypid);
	}

	/* 转换为文本形式 */
	initStringInfo(&fc_result);

	foreach(fc_cell0, fc_uniquetypes)
	{
		appendStringInfo(&fc_result, "%s\n",
						 fc_map_sql_type_to_xmlschema_type(lfirst_oid(fc_cell0),
														-1));
	}

	return fc_result.data;
}


/*
 * 将SQL数据类型映射到命名的XML Schema数据类型；参见SQL/XML:2008第9.5和9.6节。
 *
 * （9.5和9.6之间的区别基本上是9.6添加了一个名称属性，而此函数做到了这一点。没有名称的版本9.5似乎没有在任何地方被要求。）
 */
static const char * fc_map_sql_type_to_xmlschema_type(Oid fc_typeoid, int fc_typmod)
{
	StringInfoData fc_result;
	const char *fc_typename = fc_map_sql_type_to_xml_name(fc_typeoid, fc_typmod);

	initStringInfo(&fc_result);

	if (fc_typeoid == XMLOID)
	{
		appendStringInfoString(&fc_result,
							   "<xsd:complexType mixed=\"true\">\n"
							   "  <xsd:sequence>\n"
							   "    <xsd:any name=\"element\" minOccurs=\"0\" maxOccurs=\"unbounded\" processContents=\"skip\"/>\n"
							   "  </xsd:sequence>\n"
							   "</xsd:complexType>\n");
	}
	else
	{
		appendStringInfo(&fc_result,
						 "<xsd:simpleType name=\"%s\">\n", fc_typename);

		switch (fc_typeoid)
		{
			case BPCHAROID:
			case VARCHAROID:
			case TEXTOID:
				appendStringInfoString(&fc_result,
									   "  <xsd:restriction base=\"xsd:string\">\n");
				if (fc_typmod != -1)
					appendStringInfo(&fc_result,
									 "    <xsd:maxLength value=\"%d\"/>\n",
									 fc_typmod - VARHDRSZ);
				appendStringInfoString(&fc_result, "  </xsd:restriction>\n");
				break;

			case BYTEAOID:
				appendStringInfo(&fc_result,
								 "  <xsd:restriction base=\"xsd:%s\">\n"
								 "  </xsd:restriction>\n",
								 xmlbinary == XMLBINARY_BASE64 ? "base64Binary" : "hexBinary");
				break;

			case NUMERICOID:
				if (fc_typmod != -1)
					appendStringInfo(&fc_result,
									 "  <xsd:restriction base=\"xsd:decimal\">\n"
									 "    <xsd:totalDigits value=\"%d\"/>\n"
									 "    <xsd:fractionDigits value=\"%d\"/>\n"
									 "  </xsd:restriction>\n",
									 ((fc_typmod - VARHDRSZ) >> 16) & 0xffff,
									 (fc_typmod - VARHDRSZ) & 0xffff);
				break;

			case INT2OID:
				appendStringInfo(&fc_result,
								 "  <xsd:restriction base=\"xsd:short\">\n"
								 "    <xsd:maxInclusive value=\"%d\"/>\n"
								 "    <xsd:minInclusive value=\"%d\"/>\n"
								 "  </xsd:restriction>\n",
								 SHRT_MAX, SHRT_MIN);
				break;

			case INT4OID:
				appendStringInfo(&fc_result,
								 "  <xsd:restriction base=\"xsd:int\">\n"
								 "    <xsd:maxInclusive value=\"%d\"/>\n"
								 "    <xsd:minInclusive value=\"%d\"/>\n"
								 "  </xsd:restriction>\n",
								 INT_MAX, INT_MIN);
				break;

			case INT8OID:
				appendStringInfo(&fc_result,
								 "  <xsd:restriction base=\"xsd:long\">\n"
								 "    <xsd:maxInclusive value=\"" INT64_FORMAT "\"/>\n"
								 "    <xsd:minInclusive value=\"" INT64_FORMAT "\"/>\n"
								 "  </xsd:restriction>\n",
								 PG_INT64_MAX,
								 PG_INT64_MIN);
				break;

			case FLOAT4OID:
				appendStringInfoString(&fc_result,
									   "  <xsd:restriction base=\"xsd:float\"></xsd:restriction>\n");
				break;

			case FLOAT8OID:
				appendStringInfoString(&fc_result,
									   "  <xsd:restriction base=\"xsd:double\"></xsd:restriction>\n");
				break;

			case BOOLOID:
				appendStringInfoString(&fc_result,
									   "  <xsd:restriction base=\"xsd:boolean\"></xsd:restriction>\n");
				break;

			case TIMEOID:
			case TIMETZOID:
				{
					const char *fc_tz = (fc_typeoid == TIMETZOID ? "(\\+|-)\\p{Nd}{2}:\\p{Nd}{2}" : "");

					if (fc_typmod == -1)
						appendStringInfo(&fc_result,
										 "  <xsd:restriction base=\"xsd:time\">\n"
										 "    <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}(.\\p{Nd}+)?%s\"/>\n"
										 "  </xsd:restriction>\n", fc_tz);
					else if (fc_typmod == 0)
						appendStringInfo(&fc_result,
										 "  <xsd:restriction base=\"xsd:time\">\n"
										 "    <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}%s\"/>\n"
										 "  </xsd:restriction>\n", fc_tz);
					else
						appendStringInfo(&fc_result,
										 "  <xsd:restriction base=\"xsd:time\">\n"
										 "    <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}.\\p{Nd}{%d}%s\"/>\n"
										 "  </xsd:restriction>\n", fc_typmod - VARHDRSZ, fc_tz);
					break;
				}

			case TIMESTAMPOID:
			case TIMESTAMPTZOID:
				{
					const char *fc_tz = (fc_typeoid == TIMESTAMPTZOID ? "(\\+|-)\\p{Nd}{2}:\\p{Nd}{2}" : "");

					if (fc_typmod == -1)
						appendStringInfo(&fc_result,
										 "  <xsd:restriction base=\"xsd:dateTime\">\n"
										 "    <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}(.\\p{Nd}+)?%s\"/>\n"
										 "  </xsd:restriction>\n", fc_tz);
					else if (fc_typmod == 0)
						appendStringInfo(&fc_result,
										 "  <xsd:restriction base=\"xsd:dateTime\">\n"
										 "    <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}%s\"/>\n"
										 "  </xsd:restriction>\n", fc_tz);
					else
						appendStringInfo(&fc_result,
										 "  <xsd:restriction base=\"xsd:dateTime\">\n"
										 "    <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}.\\p{Nd}{%d}%s\"/>\n"
										 "  </xsd:restriction>\n", fc_typmod - VARHDRSZ, fc_tz);
					break;
				}

			case DATEOID:
				appendStringInfoString(&fc_result,
									   "  <xsd:restriction base=\"xsd:date\">\n"
									   "    <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}\"/>\n"
									   "  </xsd:restriction>\n");
				break;

			default:
				if (get_typtype(fc_typeoid) == TYPTYPE_DOMAIN)
				{
					Oid			fc_base_typeoid;
					int32		fc_base_typmod = -1;

					fc_base_typeoid = getBaseTypeAndTypmod(fc_typeoid, &fc_base_typmod);

					appendStringInfo(&fc_result,
									 "  <xsd:restriction base=\"%s\"/>\n",
									 fc_map_sql_type_to_xml_name(fc_base_typeoid, fc_base_typmod));
				}
				break;
		}
		appendStringInfoString(&fc_result, "</xsd:simpleType>\n");
	}

	return fc_result.data;
}


/*
 * 将SQL行映射到XML元素，从活动的SPI游标中获取行。另见 SQL/XML:2008 第9.10节。
 */
static void fc_SPI_sql_row_to_xmlelement(uint64 fc_rownum, StringInfo fc_result, char *fc_tablename,
						  bool fc_nulls, bool fc_tableforest,
						  const char *fc_targetns, bool fc_top_level)
{
	int			fc_i;
	char	   *fc_xmltn;

	if (fc_tablename)
		fc_xmltn = map_sql_identifier_to_xml_name(fc_tablename, true, false);
	else
	{
		if (fc_tableforest)
			fc_xmltn = "row";
		else
			fc_xmltn = "table";
	}

	if (fc_tableforest)
		fc_xmldata_root_element_start(fc_result, fc_xmltn, NULL, fc_targetns, fc_top_level);
	else
		appendStringInfoString(fc_result, "<row>\n");

	for (fc_i = 1; fc_i <= SPI_tuptable->tupdesc->natts; fc_i++)
	{
		char	   *fc_colname;
		Datum		fc_colval;
		bool		fc_isnull;

		fc_colname = map_sql_identifier_to_xml_name(SPI_fname(SPI_tuptable->tupdesc, fc_i),
												 true, false);
		fc_colval = SPI_getbinval(SPI_tuptable->vals[fc_rownum],
							   SPI_tuptable->tupdesc,
							   fc_i,
							   &fc_isnull);
		if (fc_isnull)
		{
			if (fc_nulls)
				appendStringInfo(fc_result, "  <%s xsi:nil=\"true\"/>\n", fc_colname);
		}
		else
			appendStringInfo(fc_result, "  <%s>%s</%s>\n",
							 fc_colname,
							 map_sql_value_to_xml_value(fc_colval,
														SPI_gettypeid(SPI_tuptable->tupdesc, fc_i), true),
							 fc_colname);
	}

	if (fc_tableforest)
	{
		fc_xmldata_root_element_end(fc_result, fc_xmltn);
		appendStringInfoChar(fc_result, '\n');
	}
	else
		appendStringInfoString(fc_result, "</row>\n\n");
}


/*
 * 与XPath相关的函数
 */

#ifdef USE_LIBXML


/*
 * 将XML节点转换为文本。
 *
 * 对于属性和文本节点，返回转义文本。对于其他任何内容，
 * 输出整个子树。
 */
static text * fc_xml_xmlnodetoxmltype(xmlNodePtr fc_cur, PgXmlErrorContext *fc_xmlerrcxt)
{
	xmltype    *fc_result = NULL;

	if (fc_cur->type != XML_ATTRIBUTE_NODE && fc_cur->type != XML_TEXT_NODE)
	{
		void		(*volatile fc_nodefree) (xmlNodePtr) = NULL;
		volatile xmlBufferPtr fc_buf = NULL;
		volatile xmlNodePtr fc_cur_copy = NULL;

		PG_TRY();
		{
			int			fc_bytes;

			fc_buf = xmlBufferCreate();
			if (fc_buf == NULL || fc_xmlerrcxt->err_occurred)
				xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
							"could not allocate xmlBuffer");

			/*
			 * 生成我们可以序列化的节点转储。xmlNodeDump
			 * 是这样做的，但是该函数的结果不会包含
			 * 上级节点的命名空间定义，因此我们首先进行
			 * xmlCopyNode()，这会复制节点及其所需的
			 * 命名空间定义。
			 *
			 * 一些旧的libxml2版本，如2.7.6，在
			 * 复制时会生成部分损坏的XML_DOCUMENT_NODE节点（未设置内容字段）。
			 * 这样的节点的xmlNodeDump工作正常，但
			 * xmlFreeNode崩溃；设置我们调用xmlFreeDoc代替。
 */ 
			fc_cur_copy = xmlCopyNode(fc_cur, 1);
			if (fc_cur_copy == NULL || fc_xmlerrcxt->err_occurred)
				xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
							"could not copy node");
			fc_nodefree = (fc_cur_copy->type == XML_DOCUMENT_NODE) ?
				(void (*) (xmlNodePtr)) xmlFreeDoc : xmlFreeNode;

			fc_bytes = xmlNodeDump(fc_buf, NULL, fc_cur_copy, 0, 0);
			if (fc_bytes == -1 || fc_xmlerrcxt->err_occurred)
				xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
							"could not dump node");

			fc_result = fc_xmlBuffer_to_xmltype(fc_buf);
		}
		PG_FINALLY();
		{
			if (fc_nodefree)
				fc_nodefree(fc_cur_copy);
			if (fc_buf)
				xmlBufferFree(fc_buf);
		}
		PG_END_TRY();
	}
	else
	{
		xmlChar    *fc_str;

		fc_str = xmlXPathCastNodeToString(fc_cur);
		PG_TRY();
		{
			/* 在这里我们依赖于 XML 的表示与 TEXT 相同 */
			char	   *fc_escaped = escape_xml((char *) fc_str);

			fc_result = (xmltype *) cstring_to_text(fc_escaped);
			pfree(fc_escaped);
		}
		PG_FINALLY();
		{
			xmlFree(fc_str);
		}
		PG_END_TRY();
	}

	return fc_result;
}

/*
 * 将一个 XML XPath 对象（评估 XPath 表达式的结果）
 * 转换为一个 xml 值的数组，这些值会附加到 astate。该函数
 * 的返回值是数组中的元素数量。
 *
 * 如果 "astate" 为 NULL，则我们不生成数组值，但仍然
 * 返回它本应拥有的元素数量。
 *
 * 节点集被转换为一个包含节点文本表示的数组。
 * 原始值（float，double，string）被转换为包含值的字符串表示的单元素数组。
 */
static int fc_xml_xpathobjtoxmlarray(xmlXPathObjectPtr fc_xpathobj,
					   ArrayBuildState *fc_astate,
					   PgXmlErrorContext *fc_xmlerrcxt)
{
	int			fc_result = 0;
	Datum		fc_datum;
	Oid			fc_datumtype;
	char	   *fc_result_str;

	switch (fc_xpathobj->type)
	{
		case XPATH_NODESET:
			if (fc_xpathobj->nodesetval != NULL)
			{
				fc_result = fc_xpathobj->nodesetval->nodeNr;
				if (fc_astate != NULL)
				{
					int			fc_i;

					for (fc_i = 0; fc_i < fc_result; fc_i++)
					{
						fc_datum = PointerGetDatum(fc_xml_xmlnodetoxmltype(fc_xpathobj->nodesetval->nodeTab[fc_i],
																	 fc_xmlerrcxt));
						(void) accumArrayResult(fc_astate, fc_datum, false,
												XMLOID, CurrentMemoryContext);
					}
				}
			}
			return fc_result;

		case XPATH_BOOLEAN:
			if (fc_astate == NULL)
				return 1;
			fc_datum = BoolGetDatum(fc_xpathobj->boolval);
			fc_datumtype = BOOLOID;
			break;

		case XPATH_NUMBER:
			if (fc_astate == NULL)
				return 1;
			fc_datum = Float8GetDatum(fc_xpathobj->floatval);
			fc_datumtype = FLOAT8OID;
			break;

		case XPATH_STRING:
			if (fc_astate == NULL)
				return 1;
			fc_datum = CStringGetDatum((char *) fc_xpathobj->stringval);
			fc_datumtype = CSTRINGOID;
			break;

		default:
			elog(ERROR, "xpath expression result type %d is unsupported",
				 fc_xpathobj->type);
			return 0;			/* 保持编译器安静 */
	}

	/* 标量值情况下的公共代码 */
	fc_result_str = map_sql_value_to_xml_value(fc_datum, fc_datumtype, true);
	fc_datum = PointerGetDatum(fc_cstring_to_xmltype(fc_result_str));
	(void) accumArrayResult(fc_astate, fc_datum, false,
							XMLOID, CurrentMemoryContext);
	return 1;
}


/*
 * xpath() 和 xmlexists() 的公共代码
 *
 * 评估 XPath 表达式，并在 res_nitems 中返回节点数量
 * 和在 astate 中的 XML 值数组。任何一个指针可以是 
 * NULL，如果不需要相应的结果。
 *
 * 用户必须确保传入的 XML 确实是一个
 * XML 文档 - XPath 在不知道上下文节点的情况下不容易在片段上工作。
 */
static void fc_xpath_internal(text *fc_xpath_expr_text, xmltype *fc_data, ArrayType *fc_namespaces,
			   int *fc_res_nitems, ArrayBuildState *fc_astate)
{
	PgXmlErrorContext *fc_xmlerrcxt;
	volatile xmlParserCtxtPtr fc_ctxt = NULL;
	volatile xmlDocPtr fc_doc = NULL;
	volatile xmlXPathContextPtr fc_xpathctx = NULL;
	volatile xmlXPathCompExprPtr fc_xpathcomp = NULL;
	volatile xmlXPathObjectPtr fc_xpathobj = NULL;
	char	   *fc_datastr;
	int32		fc_len;
	int32		fc_xpath_len;
	xmlChar    *fc_string;
	xmlChar    *fc_xpath_expr;
	size_t		fc_xmldecl_len = 0;
	int			fc_i;
	int			fc_ndim;
	Datum	   *fc_ns_names_uris;
	bool	   *fc_ns_names_uris_nulls;
	int			fc_ns_count;

	/*
	 * 命名空间映射作为 text[] 传递。如果传入一个空数组
	 * (ndim = 0, “0维”)，则没有命名空间映射。
	 * 否则，应传入一个长度第二维等于
	 * 2 的二维数组，即，每个子数组包含 2 个元素，第
	 * 一个元素定义名称，第二个元素定义 URI。示例：
	 * ARRAY[ARRAY['myns', 'http://example.com'], ARRAY['myns2',
	 * 'http://example2.com']].
	 */
	fc_ndim = fc_namespaces ? ARR_NDIM(fc_namespaces) : 0;
	if (fc_ndim != 0)
	{
		int		   *fc_dims;

		fc_dims = ARR_DIMS(fc_namespaces);

		if (fc_ndim != 2 || fc_dims[1] != 2)
			ereport(ERROR,
					(errcode(ERRCODE_DATA_EXCEPTION),
					 errmsg("invalid array for XML namespace mapping"),
					 errdetail("The array must be two-dimensional with length of the second axis equal to 2.")));

		Assert(ARR_ELEMTYPE(fc_namespaces) == TEXTOID);

		deconstruct_array(fc_namespaces, TEXTOID, -1, false, TYPALIGN_INT,
						  &fc_ns_names_uris, &fc_ns_names_uris_nulls,
						  &fc_ns_count);

		Assert((fc_ns_count % 2) == 0);	/* 上面已检查 */
		fc_ns_count /= 2;			/* 仅计算对 */
	}
	else
	{
		fc_ns_names_uris = NULL;
		fc_ns_names_uris_nulls = NULL;
		fc_ns_count = 0;
	}

	fc_datastr = VARDATA(fc_data);
	fc_len = VARSIZE(fc_data) - VARHDRSZ;
	fc_xpath_len = VARSIZE_ANY_EXHDR(fc_xpath_expr_text);
	if (fc_xpath_len == 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATA_EXCEPTION),
				 errmsg("empty XPath expression")));

	fc_string = pg_xmlCharStrndup(fc_datastr, fc_len);
	fc_xpath_expr = pg_xmlCharStrndup(VARDATA_ANY(fc_xpath_expr_text), fc_xpath_len);

	/*
	 * 在一个 UTF8 数据库中，跳过任何 XML 声明，这可能会声明
	 * 另一种编码。忽略 parse_xml_decl() 的失败，让
	 * xmlCtxtReadMemory() 报告解析错误。文档免责声明
	 * xpath() 对于非 ASCII 数据在非 UTF8 数据库中的支持，
	 * 所以要将这些场景保持与历史行为兼容。
	 */
	if (GetDatabaseEncoding() == PG_UTF8)
		fc_parse_xml_decl(fc_string, &fc_xmldecl_len, NULL, NULL, NULL);

	fc_xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);

	PG_TRY();
	{
		xmlInitParser();

		/*
		 * 冗余的 XML 解析（在一次命令执行中对同一值进行两次解析是可能的）
		 */
		fc_ctxt = xmlNewParserCtxt();
		if (fc_ctxt == NULL || fc_xmlerrcxt->err_occurred)
			xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
						"could not allocate parser context");
		fc_doc = xmlCtxtReadMemory(fc_ctxt, (char *) fc_string + fc_xmldecl_len,
								fc_len - fc_xmldecl_len, NULL, NULL, 0);
		if (fc_doc == NULL || fc_xmlerrcxt->err_occurred)
			xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
						"could not parse XML document");
		fc_xpathctx = xmlXPathNewContext(fc_doc);
		if (fc_xpathctx == NULL || fc_xmlerrcxt->err_occurred)
			xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
						"could not allocate XPath context");
		fc_xpathctx->node = (xmlNodePtr) fc_doc;

		/* 注册命名空间（如果有的话） */
		if (fc_ns_count > 0)
		{
			for (fc_i = 0; fc_i < fc_ns_count; fc_i++)
			{
				char	   *fc_ns_name;
				char	   *fc_ns_uri;

				if (fc_ns_names_uris_nulls[fc_i * 2] ||
					fc_ns_names_uris_nulls[fc_i * 2 + 1])
					ereport(ERROR,
							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
							 errmsg("neither namespace name nor URI may be null")));
				fc_ns_name = TextDatumGetCString(fc_ns_names_uris[fc_i * 2]);
				fc_ns_uri = TextDatumGetCString(fc_ns_names_uris[fc_i * 2 + 1]);
				if (xmlXPathRegisterNs(fc_xpathctx,
									   (xmlChar *) fc_ns_name,
									   (xmlChar *) fc_ns_uri) != 0)
					ereport(ERROR,	/* 这是内部错误吗??? */
							(errmsg("could not register XML namespace with name \"%s\" and URI \"%s\"",
									fc_ns_name, fc_ns_uri)));
			}
		}

		/*
		 * 注意：在这里和其他地方，要小心使用 xmlXPathCtxtCompile 而不是
		 * xmlXPathCompile。在 libxml2 2.13.3 及更早版本中，后者函数
		 * 无法防范递归导致栈溢出。参见
		 * https://gitlab.gnome.org/GNOME/libxml2/-/issues/799
		 */
		fc_xpathcomp = xmlXPathCtxtCompile(fc_xpathctx, fc_xpath_expr);
		if (fc_xpathcomp == NULL || fc_xmlerrcxt->err_occurred)
			xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
						"invalid XPath expression");

		/*
		 * 版本 2.6.27 引入了一个名为
		 * xmlXPathCompiledEvalToBoolean 的函数，这对于 xmlexists 来说已经足够，
		 * 但我们可以通过返回的节点数量来推断存在性，
		 * 从而避免库版本升级并保持代码不变。
		 */
		fc_xpathobj = xmlXPathCompiledEval(fc_xpathcomp, fc_xpathctx);
		if (fc_xpathobj == NULL || fc_xmlerrcxt->err_occurred)
			xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
						"could not create XPath object");

		/*
		 * 按要求提取结果。
		 */
		if (fc_res_nitems != NULL)
			*fc_res_nitems = fc_xml_xpathobjtoxmlarray(fc_xpathobj, fc_astate, fc_xmlerrcxt);
		else
			(void) fc_xml_xpathobjtoxmlarray(fc_xpathobj, fc_astate, fc_xmlerrcxt);
	}
	PG_CATCH();
	{
		if (fc_xpathobj)
			xmlXPathFreeObject(fc_xpathobj);
		if (fc_xpathcomp)
			xmlXPathFreeCompExpr(fc_xpathcomp);
		if (fc_xpathctx)
			xmlXPathFreeContext(fc_xpathctx);
		if (fc_doc)
			xmlFreeDoc(fc_doc);
		if (fc_ctxt)
			xmlFreeParserCtxt(fc_ctxt);

		pg_xml_done(fc_xmlerrcxt, true);

		PG_RE_THROW();
	}
	PG_END_TRY();

	xmlXPathFreeObject(fc_xpathobj);
	xmlXPathFreeCompExpr(fc_xpathcomp);
	xmlXPathFreeContext(fc_xpathctx);
	xmlFreeDoc(fc_doc);
	xmlFreeParserCtxt(fc_ctxt);

	pg_xml_done(fc_xmlerrcxt, false);
}
#endif							/* USE_LIBXML */

/*
 * 评估 XPath 表达式并返回 XML 值数组。
 *
 * 由于我们尚未支持 XQuery 序列，因此这个函数似乎
 * 是最有用的一个（XML 函数数组在某种程度上充当
 * XQuery 序列的替代）。
 */
Datum xpath(PG_FUNCTION_ARGS)
{
#ifdef USE_LIBXML
	text	   *fc_xpath_expr_text = PG_GETARG_TEXT_PP(0);
	xmltype    *fc_data = PG_GETARG_XML_P(1);
	ArrayType  *fc_namespaces = PG_GETARG_ARRAYTYPE_P(2);
	ArrayBuildState *fc_astate;

	fc_astate = initArrayResult(XMLOID, CurrentMemoryContext, true);
	fc_xpath_internal(fc_xpath_expr_text, fc_data, fc_namespaces,
				   NULL, fc_astate);
	PG_RETURN_ARRAYTYPE_P(makeArrayResult(fc_astate, CurrentMemoryContext));
#else
	NO_XML_SUPPORT();
	return 0;
#endif
}

/*
 * 确定由提供的 XPath 指定的节点是否存在于给定的 XML 文档中，
 * 返回一个布尔值。
 */
Datum xmlexists(PG_FUNCTION_ARGS)
{
#ifdef USE_LIBXML
	text	   *fc_xpath_expr_text = PG_GETARG_TEXT_PP(0);
	xmltype    *fc_data = PG_GETARG_XML_P(1);
	int			fc_res_nitems;

	fc_xpath_internal(fc_xpath_expr_text, fc_data, NULL,
				   &fc_res_nitems, NULL);

	PG_RETURN_BOOL(fc_res_nitems > 0);
#else
	NO_XML_SUPPORT();
	return 0;
#endif
}

/*
 * 确定由提供的 XPath 指定的节点是否存在于给定的 XML 文档中，
 * 返回一个布尔值。与 xmlexists 不同的是它支持命名空间，并且在 SQL/XML 中未定义。
 */
Datum xpath_exists(PG_FUNCTION_ARGS)
{
#ifdef USE_LIBXML
	text	   *fc_xpath_expr_text = PG_GETARG_TEXT_PP(0);
	xmltype    *fc_data = PG_GETARG_XML_P(1);
	ArrayType  *fc_namespaces = PG_GETARG_ARRAYTYPE_P(2);
	int			fc_res_nitems;

	fc_xpath_internal(fc_xpath_expr_text, fc_data, fc_namespaces,
				   &fc_res_nitems, NULL);

	PG_RETURN_BOOL(fc_res_nitems > 0);
#else
	NO_XML_SUPPORT();
	return 0;
#endif
}

/*
 * 检查结构正确性的函数
 */

#ifdef USE_LIBXML
static bool fc_wellformed_xml(text *fc_data, XmlOptionType fc_xmloption_arg)
{
	bool		fc_result;
	volatile xmlDocPtr fc_doc = NULL;

	/* 我们希望捕获任何异常并返回 false */
	PG_TRY();
	{
		fc_doc = fc_xml_parse(fc_data, fc_xmloption_arg, true, GetDatabaseEncoding());
		fc_result = true;
	}
	PG_CATCH();
	{
		FlushErrorState();
		fc_result = false;
	}
	PG_END_TRY();

	if (fc_doc)
		xmlFreeDoc(fc_doc);

	return fc_result;
}
#endif

Datum xml_is_well_formed(PG_FUNCTION_ARGS)
{
#ifdef USE_LIBXML
	text	   *fc_data = PG_GETARG_TEXT_PP(0);

	PG_RETURN_BOOL(fc_wellformed_xml(fc_data, xmloption));
#else
	NO_XML_SUPPORT();
	return 0;
#endif							/* 非 USE_LIBXML */
}

Datum xml_is_well_formed_document(PG_FUNCTION_ARGS)
{
#ifdef USE_LIBXML
	text	   *fc_data = PG_GETARG_TEXT_PP(0);

	PG_RETURN_BOOL(fc_wellformed_xml(fc_data, XMLOPTION_DOCUMENT));
#else
	NO_XML_SUPPORT();
	return 0;
#endif							/* 非 USE_LIBXML */
}

Datum xml_is_well_formed_content(PG_FUNCTION_ARGS)
{
#ifdef USE_LIBXML
	text	   *fc_data = PG_GETARG_TEXT_PP(0);

	PG_RETURN_BOOL(fc_wellformed_xml(fc_data, XMLOPTION_CONTENT));
#else
	NO_XML_SUPPORT();
	return 0;
#endif							/* 非 USE_LIBXML */
}

/*
 * XMLTABLE 的支持函数
 *
 */
#ifdef USE_LIBXML

/*
 * 从执行器状态返回私有数据。通过检查
 * MAGIC 数字以确保有效性。
 */
static inline XmlTableBuilderData *
fc_GetXmlTableBuilderPrivateData(TableFuncScanState *fc_state, const char *fc_fname)
{
	XmlTableBuilderData *fc_result;

	if (!IsA(fc_state, TableFuncScanState))
		elog(ERROR, "%s called with invalid TableFuncScanState", fc_fname);
	fc_result = (XmlTableBuilderData *) fc_state->opaque;
	if (fc_result->magic != XMLTABLE_CONTEXT_MAGIC)
		elog(ERROR, "%s called with invalid TableFuncScanState", fc_fname);

	return fc_result;
}
#endif

/*
 * XmlTableInitOpaque
 *		填充 TableFuncScanState->opaque 以供 XmlTable 处理器使用；
 *		初始化 XML 解析器。
 *
 * 注意：因为我们在这里调用 pg_xml_init()，而在
 * XmlTableDestroyOpaque 中调用 pg_xml_done()，
 * 所以在此节点处理完成之前，不能有其他执行器节点运行。
 * 调用者必须完成此操作（可能填充一个 tuplestore 
 * 来一次性耗尽此节点），而不是使用逐行调用模式。
 */
static void fc_XmlTableInitOpaque(TableFuncScanState *fc_state, int fc_natts)
{
#ifdef USE_LIBXML
	volatile xmlParserCtxtPtr fc_ctxt = NULL;
	XmlTableBuilderData *fc_xtCxt;
	PgXmlErrorContext *fc_xmlerrcxt;

	fc_xtCxt = palloc0(sizeof(XmlTableBuilderData));
	fc_xtCxt->magic = XMLTABLE_CONTEXT_MAGIC;
	fc_xtCxt->natts = fc_natts;
	fc_xtCxt->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * fc_natts);

	fc_xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);

	PG_TRY();
	{
		xmlInitParser();

		fc_ctxt = xmlNewParserCtxt();
		if (fc_ctxt == NULL || fc_xmlerrcxt->err_occurred)
			xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
						"could not allocate parser context");
	}
	PG_CATCH();
	{
		if (fc_ctxt != NULL)
			xmlFreeParserCtxt(fc_ctxt);

		pg_xml_done(fc_xmlerrcxt, true);

		PG_RE_THROW();
	}
	PG_END_TRY();

	fc_xtCxt->xmlerrcxt = fc_xmlerrcxt;
	fc_xtCxt->ctxt = fc_ctxt;

	fc_state->opaque = fc_xtCxt;
#else
	NO_XML_SUPPORT();
#endif							/* 非 USE_LIBXML */
}

/*
 * XmlTableSetDocument
 *		安装输入文档
 */
static void fc_XmlTableSetDocument(TableFuncScanState *fc_state, Datum fc_value)
{
#ifdef USE_LIBXML
	XmlTableBuilderData *fc_xtCxt;
	xmltype    *fc_xmlval = DatumGetXmlP(fc_value);
	char	   *fc_str;
	xmlChar    *fc_xstr;
	int			fc_length;
	volatile xmlDocPtr fc_doc = NULL;
	volatile xmlXPathContextPtr fc_xpathcxt = NULL;

	fc_xtCxt = fc_GetXmlTableBuilderPrivateData(fc_state, "XmlTableSetDocument");

	/*
	 * 使用外部函数进行字符串转换（去除编码属性）。参见
	 * xml_out 中的注释。
	 */
	fc_str = fc_xml_out_internal(fc_xmlval, 0);

	fc_length = strlen(fc_str);
	fc_xstr = pg_xmlCharStrndup(fc_str, fc_length);

	PG_TRY();
	{
		fc_doc = xmlCtxtReadMemory(fc_xtCxt->ctxt, (char *) fc_xstr, fc_length, NULL, NULL, 0);
		if (fc_doc == NULL || fc_xtCxt->xmlerrcxt->err_occurred)
			xml_ereport(fc_xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
						"could not parse XML document");
		fc_xpathcxt = xmlXPathNewContext(fc_doc);
		if (fc_xpathcxt == NULL || fc_xtCxt->xmlerrcxt->err_occurred)
			xml_ereport(fc_xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
						"could not allocate XPath context");
		fc_xpathcxt->node = (xmlNodePtr) fc_doc;
	}
	PG_CATCH();
	{
		if (fc_xpathcxt != NULL)
			xmlXPathFreeContext(fc_xpathcxt);
		if (fc_doc != NULL)
			xmlFreeDoc(fc_doc);

		PG_RE_THROW();
	}
	PG_END_TRY();

	fc_xtCxt->doc = fc_doc;
	fc_xtCxt->xpathcxt = fc_xpathcxt;
#else
	NO_XML_SUPPORT();
#endif							/* 非 USE_LIBXML */
}

/*
 * XmlTableSetNamespace
 *		添加命名空间声明
 */
static void fc_XmlTableSetNamespace(TableFuncScanState *fc_state, const char *fc_name, const char *fc_uri)
{
#ifdef USE_LIBXML
	XmlTableBuilderData *fc_xtCxt;

	if (fc_name == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("DEFAULT namespace is not supported")));
	fc_xtCxt = fc_GetXmlTableBuilderPrivateData(fc_state, "XmlTableSetNamespace");

	if (xmlXPathRegisterNs(fc_xtCxt->xpathcxt,
						   pg_xmlCharStrndup(fc_name, strlen(fc_name)),
						   pg_xmlCharStrndup(fc_uri, strlen(fc_uri))))
		xml_ereport(fc_xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
					"could not set XML namespace");
#else
	NO_XML_SUPPORT();
#endif							/* 非 USE_LIBXML */
}

/*
 * XmlTableSetRowFilter
 *		安装行过滤器的 XPath 表达式。
*/
static void fc_XmlTableSetRowFilter(TableFuncScanState *fc_state, const char *fc_path)
{
#ifdef USE_LIBXML
	XmlTableBuilderData *fc_xtCxt;
	xmlChar    *fc_xstr;

	fc_xtCxt = fc_GetXmlTableBuilderPrivateData(fc_state, "XmlTableSetRowFilter");

	if (*fc_path == '\0')
		ereport(ERROR,
				(errcode(ERRCODE_DATA_EXCEPTION),
				 errmsg("row path filter must not be empty string")));

	fc_xstr = pg_xmlCharStrndup(fc_path, strlen(fc_path));

	/* 我们要求 XmlTableSetDocument 已经完成 */
	Assert(fc_xtCxt->xpathcxt != NULL);

	fc_xtCxt->xpathcomp = xmlXPathCtxtCompile(fc_xtCxt->xpathcxt, fc_xstr);
	if (fc_xtCxt->xpathcomp == NULL || fc_xtCxt->xmlerrcxt->err_occurred)
		xml_ereport(fc_xtCxt->xmlerrcxt, ERROR, ERRCODE_SYNTAX_ERROR,
					"invalid XPath expression");
#else
	NO_XML_SUPPORT();
#endif							/* 非 USE_LIBXML */
}

/*
 * XmlTableSetColumnFilter
 *		为给定列安装列过滤器的 XPath 表达式。
 */
static void fc_XmlTableSetColumnFilter(TableFuncScanState *fc_state, const char *fc_path, int fc_colnum)
{
#ifdef USE_LIBXML
	XmlTableBuilderData *fc_xtCxt;
	xmlChar    *fc_xstr;

	AssertArg(PointerIsValid(fc_path));

	fc_xtCxt = fc_GetXmlTableBuilderPrivateData(fc_state, "XmlTableSetColumnFilter");

	if (*fc_path == '\0')
		ereport(ERROR,
				(errcode(ERRCODE_DATA_EXCEPTION),
				 errmsg("column path filter must not be empty string")));

	fc_xstr = pg_xmlCharStrndup(fc_path, strlen(fc_path));

	/* 我们要求 XmlTableSetDocument 已经完成 */
	Assert(fc_xtCxt->xpathcxt != NULL);

	fc_xtCxt->xpathscomp[fc_colnum] = xmlXPathCtxtCompile(fc_xtCxt->xpathcxt, fc_xstr);
	if (fc_xtCxt->xpathscomp[fc_colnum] == NULL || fc_xtCxt->xmlerrcxt->err_occurred)
		xml_ereport(fc_xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
					"invalid XPath expression");
#else
	NO_XML_SUPPORT();
#endif							/* 非 USE_LIBXML */
}

/*
 * XmlTableFetchRow
 *		为即将到来的 GetValue 调用准备下一个“当前”元组。
 *		如果行过滤器表达式返回没有更多行，则返回 false。
 */
static bool fc_XmlTableFetchRow(TableFuncScanState *fc_state)
{
#ifdef USE_LIBXML
	XmlTableBuilderData *fc_xtCxt;

	fc_xtCxt = fc_GetXmlTableBuilderPrivateData(fc_state, "XmlTableFetchRow");

	
	xmlSetStructuredErrorFunc((void *) fc_xtCxt->xmlerrcxt, fc_xml_errorHandler);

	if (fc_xtCxt->xpathobj == NULL)
	{
		fc_xtCxt->xpathobj = xmlXPathCompiledEval(fc_xtCxt->xpathcomp, fc_xtCxt->xpathcxt);
		if (fc_xtCxt->xpathobj == NULL || fc_xtCxt->xmlerrcxt->err_occurred)
			xml_ereport(fc_xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
						"could not create XPath object");

		fc_xtCxt->row_count = 0;
	}

	if (fc_xtCxt->xpathobj->type == XPATH_NODESET)
	{
		if (fc_xtCxt->xpathobj->nodesetval != NULL)
		{
			if (fc_xtCxt->row_count++ < fc_xtCxt->xpathobj->nodesetval->nodeNr)
				return true;
		}
	}

	return false;
#else
	NO_XML_SUPPORT();
	return false;
#endif							/* 非 USE_LIBXML */
}

/*********************************************************************************
 * XmlTableGetValue
 *      返回当前行的列号 'colnum' 的值。如果请求的是列 -1，返回整行的表示。
 *
 * 这会导致内存泄漏，因此要确保经常重置调用它的上下文。
 */
static Datum fc_XmlTableGetValue(TableFuncScanState *fc_state, int fc_colnum,
				 Oid fc_typid, int32 fc_typmod, bool *fc_isnull)
{
#ifdef USE_LIBXML
	XmlTableBuilderData *fc_xtCxt;
	Datum		fc_result = (Datum) 0;
	xmlNodePtr	fc_cur;
	char	   *fc_cstr = NULL;
	volatile xmlXPathObjectPtr fc_xpathobj = NULL;

	fc_xtCxt = fc_GetXmlTableBuilderPrivateData(fc_state, "XmlTableGetValue");

	Assert(fc_xtCxt->xpathobj &&
		   fc_xtCxt->xpathobj->type == XPATH_NODESET &&
		   fc_xtCxt->xpathobj->nodesetval != NULL);

	
	xmlSetStructuredErrorFunc((void *) fc_xtCxt->xmlerrcxt, fc_xml_errorHandler);

	*fc_isnull = false;

	fc_cur = fc_xtCxt->xpathobj->nodesetval->nodeTab[fc_xtCxt->row_count - 1];

	Assert(fc_xtCxt->xpathscomp[fc_colnum] != NULL);

	PG_TRY();
	{
		/* 将当前节点设为 XPath 评估的入口点 */
		fc_xtCxt->xpathcxt->node = fc_cur;

		/* 评估列路径 */
		fc_xpathobj = xmlXPathCompiledEval(fc_xtCxt->xpathscomp[fc_colnum], fc_xtCxt->xpathcxt);
		if (fc_xpathobj == NULL || fc_xtCxt->xmlerrcxt->err_occurred)
			xml_ereport(fc_xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
						"could not create XPath object");

		/*
         * 依据 XPath 表达式返回的节点数量和目标列的类型，有四种可能情况：
         * a) XPath 返回没有节点。b) 目标类型为 XML（将所有内容作为 XML 返回）。对于非 XML 返回类型：
         * c) 一个节点（返回内容）。d) 多个节点（错误）。
         */
		if (fc_xpathobj->type == XPATH_NODESET)
		{
			int			fc_count = 0;

			if (fc_xpathobj->nodesetval != NULL)
				fc_count = fc_xpathobj->nodesetval->nodeNr;

			if (fc_xpathobj->nodesetval == NULL || fc_count == 0)
			{
				*fc_isnull = true;
			}
			else
			{
				if (fc_typid == XMLOID)
				{
					text	   *fc_textstr;
					StringInfoData fc_str;

					/* 连接序列化的值 */
					initStringInfo(&fc_str);
					for (int fc_i = 0; fc_i < fc_count; fc_i++)
					{
						fc_textstr =
							fc_xml_xmlnodetoxmltype(fc_xpathobj->nodesetval->nodeTab[fc_i],
												 fc_xtCxt->xmlerrcxt);

						fc_appendStringInfoText(&fc_str, fc_textstr);
					}
					fc_cstr = fc_str.data;
				}
				else
				{
					xmlChar    *fc_str;

					if (fc_count > 1)
						ereport(ERROR,
								(errcode(ERRCODE_CARDINALITY_VIOLATION),
								 errmsg("more than one value returned by column XPath expression")));

					fc_str = xmlXPathCastNodeSetToString(fc_xpathobj->nodesetval);
					fc_cstr = fc_str ? fc_xml_pstrdup_and_free(fc_str) : "";
				}
			}
		}
		else if (fc_xpathobj->type == XPATH_STRING)
		{
			/* 当目标为 XML 时，内容应被转义 */
			if (fc_typid == XMLOID)
				fc_cstr = escape_xml((char *) fc_xpathobj->stringval);
			else
				fc_cstr = (char *) fc_xpathobj->stringval;
		}
		else if (fc_xpathobj->type == XPATH_BOOLEAN)
		{
			char		fc_typcategory;
			bool		fc_typispreferred;
			xmlChar    *fc_str;

			/* 允许从布尔值隐式转换为数字 */
			get_type_category_preferred(fc_typid, &fc_typcategory, &fc_typispreferred);

			if (fc_typcategory != TYPCATEGORY_NUMERIC)
				fc_str = xmlXPathCastBooleanToString(fc_xpathobj->boolval);
			else
				fc_str = xmlXPathCastNumberToString(xmlXPathCastBooleanToNumber(fc_xpathobj->boolval));

			fc_cstr = fc_xml_pstrdup_and_free(fc_str);
		}
		else if (fc_xpathobj->type == XPATH_NUMBER)
		{
			xmlChar    *fc_str;

			fc_str = xmlXPathCastNumberToString(fc_xpathobj->floatval);
			fc_cstr = fc_xml_pstrdup_and_free(fc_str);
		}
		else
			elog(ERROR, "unexpected XPath object type %u", fc_xpathobj->type);

		/*
         * 到这里，要么 cstr 包含结果值，要么 isnull 标志已被设置。
         */
		Assert(fc_cstr || *fc_isnull);

		if (!*fc_isnull)
			fc_result = InputFunctionCall(&fc_state->in_functions[fc_colnum],
									   fc_cstr,
									   fc_state->typioparams[fc_colnum],
									   fc_typmod);
	}
	PG_FINALLY();
	{
		if (fc_xpathobj != NULL)
			xmlXPathFreeObject(fc_xpathobj);
	}
	PG_END_TRY();

	return fc_result;
#else
	NO_XML_SUPPORT();
	return 0;
#endif							/* 非 USE_LIBXML */
}

/*
 * XmlTableDestroyOpaque
 *      释放所有 libxml2 资源
 */
static void fc_XmlTableDestroyOpaque(TableFuncScanState *fc_state)
{
#ifdef USE_LIBXML
	XmlTableBuilderData *fc_xtCxt;

	fc_xtCxt = fc_GetXmlTableBuilderPrivateData(fc_state, "XmlTableDestroyOpaque");

	
	xmlSetStructuredErrorFunc((void *) fc_xtCxt->xmlerrcxt, fc_xml_errorHandler);

	if (fc_xtCxt->xpathscomp != NULL)
	{
		int			fc_i;

		for (fc_i = 0; fc_i < fc_xtCxt->natts; fc_i++)
			if (fc_xtCxt->xpathscomp[fc_i] != NULL)
				xmlXPathFreeCompExpr(fc_xtCxt->xpathscomp[fc_i]);
	}

	if (fc_xtCxt->xpathobj != NULL)
		xmlXPathFreeObject(fc_xtCxt->xpathobj);
	if (fc_xtCxt->xpathcomp != NULL)
		xmlXPathFreeCompExpr(fc_xtCxt->xpathcomp);
	if (fc_xtCxt->xpathcxt != NULL)
		xmlXPathFreeContext(fc_xtCxt->xpathcxt);
	if (fc_xtCxt->doc != NULL)
		xmlFreeDoc(fc_xtCxt->doc);
	if (fc_xtCxt->ctxt != NULL)
		xmlFreeParserCtxt(fc_xtCxt->ctxt);

	pg_xml_done(fc_xtCxt->xmlerrcxt, true);

	/* 不再有效 */
	fc_xtCxt->magic = 0;
	fc_state->opaque = NULL;

#else
	NO_XML_SUPPORT();
#endif							/* 非 USE_LIBXML */
}
